The Lambda expression is an anonymous notation that represents something that performs an operation or calculation. In functional programming, the Lambda expression is useful to produce the first class and pure function, which we will discuss in separate chapters in this book. For now, let's familiarize ourselves with this new feature introduced in C++11 by investigating three basic parts of the Lambda expression:
- capturing list: []
- parameter list: ()
- body: {}
The order of these three basic parts is as follows:
[](){}
The capturing list part is also used as a mark to identify the Lambda expression. It is a placeholder to value to be involved in the expression. The only capture defaults are the ampersand symbol (&
), which will implicitly capture the automatic variables by reference, and the equal sign (=
), which will implicitly capture the automatic variables by copy (we will discuss it further in the upcoming section). The parameter list is similar to the capturing list in every function where we can pass the value to it. The body is the implementation of the function itself.
Imagine we have a tiny one-line function that we invoke only once. It's better if we write the operation of that function directly when we need it. We actually had this function in our previous example when discussing the C++ Standard Library. Just go back to the for_each.cpp
file and we will find the PrintOut()
function that is only invoked once by for_each()
. We can make this for_each
loop more readable if we use Lambda. Let's take a look at the following code snippet to examine how we refactor the for_each.cpp
file:
/* lambda_tiny_func.cpp */ #include <vector> #include <algorithm> #include <iostream> #include "../vehicle/vehicle.h" using namespace std; auto main() -> int { cout << "[lambda_tiny_func.cpp]" << endl; // Initializing several Vehicle instances Vehicle car("car", 4); Vehicle motorcycle("motorcycle", 2); Vehicle bicycle("bicycle", 2); Vehicle bus("bus", 6); // Assigning the preceding Vehicle instances to a vector vector<Vehicle> vehicles = { car, motorcycle, bicycle, bus }; // Displaying the elements of the vector // using Lambda expression cout << "All vehicles:" << endl; for_each( begin(vehicles), end(vehicles), [](const Vehicle &vehicle){ cout << vehicle.GetType() << endl; }); return 0; }
As we can see, we have transformed the PrintOut()
function that we used in the for_each.cpp
file into a Lambda expression and passed it to the for_each
loop. It will indeed give the same output as the for_each.cpp
file does. However, now our code becomes more concise and readable.
The Lambda expression can also be used for multiline functions, so we can put the body of the function on it. This will make our code more readable as well. Let's make a new code. In that code, we will have an integer collection and an intent to inspect whether the selected element is the prime number or not. We can make a separate function, for instance, PrintPrime()
, then invoke it. However, since the prime number checking operation is called only once, it's more readable if we transform it into a Lambda expression. The code should look like this:
/* lambda_multiline_func.cpp */ #include <vector> #include <algorithm> #include <iostream> using namespace std; auto main() -> int { cout << "[lambda_multiline_func.cpp]" << endl; // Initializing a vector containing integer element vector<int> vect; for (int i = 0; i < 10; ++i) vect.push_back(i); // Displaying whether or not the element is prime number for_each( begin(vect), end(vect), [](int n) { cout << n << " is"; if(n < 2) { if(n == 0) cout << " not"; } else { for (int j = 2; j < n; ++j) { if (n % j == 0) { cout << " not"; break; } } } cout << " prime number" << endl; }); return 0; }
The output we should see on the screen is as follows:
As we can see in the preceding screenshot, we have successfully identified the prime number by using the Lambda expression.
Our two preceding samples of the Lambda expression are just for the purpose to print on console. It means the function does not need to return any value. However, we can ask the Lambda expression to return a value for an instance if we do the calculation inside the function and return the calculation result. Let's take a look at the following code to examine the use of this Lambda:
/* lambda_returning_value.cpp */ #include <vector> #include <algorithm> #include <iostream> using namespace std; auto main() -> int { cout << "[lambda_returning_value.cpp]" << endl; // Initializing a vector containing integer element vector<int> vect; for (int i = 0; i < 10; ++i) vect.push_back(i); // Displaying the elements of vect cout << "Original Data:" << endl; for_each( begin(vect), end(vect), [](int n){ cout << n << " "; }); cout << endl; // Creating another vect2 vector vector<int> vect2; // Resize the size of vect2 exactly same with vect vect2.resize(vect.size()); // Doubling the elements of vect and store to vect2 transform( begin(vect), end(vect), begin(vect2), [](int n) { return n * n; }); // Displaying the elements of vect2 cout << "Squared Data:" << endl; for_each( begin(vect2), end(vect2), [](int n) { cout << n << " "; }); cout << endl; // Creating another vect3 vector vector<double> vect3; // Resize the size of vect3 exactly same with vect vect3.resize(vect.size()); // Finding the average of the elements of vect // and store to vect2 transform( begin(vect2), end(vect2), begin(vect3), [](int n) -> double { return n / 2.0; }); // Displaying the elements of vect3 cout << "Average Data:" << endl; for_each( begin(vect3), end(vect3), [](double d) { cout << d << " "; }); cout << endl; return 0; }
When we use the transform()
method in the preceding code, we have a Lambda expression that returns a value from the calculation of n * n
. However, there's no return type stated in the expression. This is because we can omit the statement of the return type since the compiler has understood that the expression will return an integer
value. So, after we have another vector, vect2
, which has the same size as vect
, we can invoke the transform()
method along with the Lambda expression, and the value of vect
will be doubled and stored in vect2
.
We can, if we want to, specify the return type to the Lambda expression. As we can see in the preceding code, we transformed the vect3
vector based on all values of the vect
vector, but now we specify the return type to double
using the arrow symbol (->
). The result of the preceding code should be like the following screenshot:
As we can see from the preceding screenshot, we have successfully found the doubled and average result using the Lambda expression.
In our previous Lambda expression examples, we keep the capturing part and the square bracket ([]
) empty since the Lambda doesn't capture anything and doesn't have any extra member variable in the anonymous object generated by the compiler. We can also specify the object we want to capture in the Lambda expression by specifying it in this square bracket. Let's take a look at the following piece of code to go through the discussion:
/* lambda_capturing_by_value.cpp */ #include <vector> #include <algorithm> #include <iostream> using namespace std; auto main() -> int { cout << "[lambda_capturing_by_value.cpp]" << endl; // Initializing a vector containing integer element vector<int> vect; for (int i = 0; i < 10; ++i) vect.push_back(i); // Displaying the elements of vect cout << "Original Data:" << endl; for_each( begin(vect), end(vect), [](int n){ cout << n << " "; }); cout << endl; // Initializing two variables int a = 2; int b = 8; // Capturing value explicitly from the two variables cout << "Printing elements between " << a; cout << " and " << b << " explicitly [a,b]:" << endl; for_each( begin(vect), end(vect), [a,b](int n){ if (n >= a && n <= b) cout << n << " "; }); cout << endl; // Modifying variable a and b a = 3; b = 7; // Capturing value implicitly from the two variables cout << "printing elements between " << a; cout << " and " << b << " implicitly[=]:" << endl; for_each( begin(vect), end(vect), [=](int n){ if (n >= a && n <= b) cout << n << " "; }); cout << endl; return 0; }
In the preceding code, we will try to capture the value in the Lambda expression, explicitly and implicitly. Let's suppose we have two variables, a
and b
, and we want to explicitly capture the values, we can specify them in the Lambda expression using the [a,b]
statement, and then using the values inside the function body. Moreover, if we wish to capture the value implicitly, just use [=]
for the capturing part and then the expression will know which variable we intend to use when we specify them in the function body. If we run the preceding code, we will get the following output on the screen:
We can also mutate the state of the values we capture without modifying the value outside the Lambda expression function body. For this purpose, we can use the same techniques as used previously, and add the mutable
keyword as shown in the following block of code:
/* lambda_capturing_by_value_mutable.cpp */ #include <vector> #include <algorithm> #include <iostream> using namespace std; auto main() -> int { cout << "[lambda_capturing_by_value_mutable.cpp]" << endl; // Initializing a vector containing integer element vector<int> vect; for (int i = 0; i < 10; ++i) vect.push_back(i); // Displaying the elements of vect cout << "Original Data:" << endl; for_each( begin(vect), end(vect), [](int n){ cout << n << " "; }); cout << endl; // Initializing two variables int a = 1; int b = 1; // Capturing value from the two variables // without mutate them for_each( begin(vect), end(vect), [=](int& x) mutable { const int old = x; x *= 2; a = b; b = old; }); // Displaying the elements of vect cout << "Squared Data:" << endl; for_each( begin(vect), end(vect), [](int n) { cout << n << " "; }); cout << endl << endl; // Displaying value of variable a and b cout << "a = " << a << endl; cout << "b = " << b << endl; return 0; }
The preceding code will double the element of the vect
vector. It uses capturing by value in the Lambda expression and also the mutable
keyword. As we can see, we passed the vector element by reference (int& x)
and multiplied it by two, then changed the value of a
and b
. However, since we use the mutable
keyword, the final result of a
and b
will remain the same, although, we have passed the vector by reference. The output on the console looks like the following screenshot:
If we want to change the value of the a
and b
variables, we have to use the Lambda expression to capture by reference. We can do this by passing the reference to the angle bracket in the Lambda expression, for instance, [&a, &b]
. For more detail, let's take a look at the following piece of code:
/* lambda_capturing_by_reference.cpp */ #include <vector> #include <algorithm> #include <iostream> using namespace std; auto main() -> int { cout << "[lambda_capturing_by_reference.cpp]" << endl; // Initializing a vector containing integer element vector<int> vect; for (int i = 0; i < 10; ++i) vect.push_back(i); // Displaying the elements of vect cout << "Original Data:" << endl; for_each( begin(vect), end(vect), [](int n){ cout << n << " "; }); cout << endl; // Initializing two variables int a = 1; int b = 1; // Capturing value from the two variables // and mutate them for_each( begin(vect), end(vect), [&a, &b](int& x){ const int old = x; x *= 2; a = b; b = old; }); // Displaying the elements of vect cout << "Squared Data:" << endl; for_each( begin(vect), end(vect), [](int n) { cout << n << " "; }); cout << endl << endl; // Displaying value of variable a and b cout << "a = " << a << endl; cout << "b = " << b << endl; return 0; }
The preceding code has the same behavior with the lambda_capturing_by_value_mutable.cpp
file that will double the element of the vect
vector. However, by capturing by reference, it now also modifies the value of a
and b
when they are processed in the for_each
loop. The a
and b
values will be changed at the end of the code, as we can see in the following screenshot:
Another great feature of the Lambda expression coming up in C++14 is its initialization captures. The expression can capture a value of the variable and assign it to the expression's variable. Let's take a look at the following piece of code implementing the initialization captures:
/* lambda_initialization_captures.cpp */ #include <iostream> using namespace std; auto main() -> int { cout << "[lambda_initialization_captures.cpp]" << endl; // Initializing a variable int a = 5; cout << "Initial a = " << a << endl; // Initializing value to lambda using the variable auto myLambda = [&x = a]() { x += 2; }; // Executing the Lambda myLambda(); // Displaying a new value of the variable cout << "New a = " << a << endl; return 0; }
As we can see in the preceding code, we have an int variable named a
with the value 5
. The Lambda expression, myLambda
, then captures the a
value and executes it in the code. The result is that now the a
value will be 7
since it is added by 2
. The following output screenshot should appear in our console window when we run the preceding code:
From the preceding snapshot, we see that we can prepare the value to be included in the calculation inside the Lambda expression.
Before C++14, we have to specifically state the type of the parameter list. Fortunately, now in C++14, Lambda expressions accept auto
as a valid parameter type. Therefore, we can now build a generic Lambda expression as demonstrated in the following code. In that code, we have only one Lambda expression to find out which is the greatest value between two numbers passed to the expression. We will use the auto
keyword in parameter declaration so it can be passed by any data type. Therefore, the findMax()
function parameters can be passed by both the int
and float
data type. The code should be as follows:
/* lambda_expression_generic.cpp */ #include <iostream> using namespace std; auto main() -> int { cout << "[lambda_expression_generic.cpp]" << endl; // Creating a generic lambda expression auto findMax = [](auto &x, auto &y){ return x > y ? x : y; }; // Initializing various variables int i1 = 5, i2 = 3; float f1 = 2.5f, f2 = 2.05f; // Consuming generic lambda expression // using integer data type cout << "i1 = 5, i2 = 3" << endl; cout << "Max: " << findMax(i1, i2) << endl << endl; // Consuming generic lambda expression // using double data type cout << "f1 = 2.5f, f2 = 2.05f" << endl; cout << "Max: " << findMax(f1, f2) << endl << endl; return 0; }
The output we will see on the console should be as follows:
Note
The C++17 language plans to introduce two new features for the Lambda expression--they are capturing *this
, which allows the expression to capture the enclosing object by copy, and the constexpr
Lambda expressions, which allows us to use the result of the Lambda expressions and generate constexpr
objects at compile time. However, since C++17 has not been released yet, we cannot try it for now.