Lambdas
Contents
Capture Clause
The capture clause can specify how we can include variables from outside the function.
[=] (parameters) mutable throw() -> return-type {body}
1 2 3 4 5 6
1) capture clause
2) parameters
3) mutable
4) throw
5) return type
6) body
A lambda expression translates to a function object with a class
and the operator "()" in the class. It's a more convenient notation
than creating a class or an actual function. It can be used inline
or used as arguments. We can use the handy "auto" to declare the type
that the lambda expression will return.
1) capture clause This dictates the variables that we can access before the lambda function.
File: lambdas1.cpp
#include <algorithm> #include <array> #include <string> #include <functional> #include <iostream> using namespace std ; class Integer { public: int x1 ; Integer(int x1p) { x1 = x1p ; cout << "Object Created" << x1 << endl; } Integer(const Integer& param1) { x1 = param1.x1 ; cout << "Copy constructor: Object Created" << x1 << endl; } ~Integer() { cout << "Object Destroyed: " << x1 << endl; } }; int main() { int x1 = 10 ; int x2 = 10 ; //outside variable like x1 is not captured //auto print1 = [](string str1){ auto print1 = [](string str1){ cout << str1 << endl ; } ; print1( "Calling the lambda print1 function." ) ; //Trying to access the variable outside the //scope auto print2 = [](string str1){ cout << str1 << endl ; // cout << x1 << endl ; } ; print2( "Calling the lambda print2 function." ) ; auto print3 = [=](string str1){ cout << str1 << endl ; cout << x1 << endl ; //x1=5 ; Compiler error //x1 is read only } ; print3( "Calling the lambda print3 function." ) ; auto print4 = [&](string str1){ cout << str1 << endl ; x1=4 ; } ; print4( "Calling the lambda print4 function." ) ; cout << x1 << endl ; auto print5 = [x1](string str1){ cout << str1 << endl ; cout << x1 << endl ; //cout << x2 << endl ; //The "x2" is not accessible here. //[&x1] will pass x1 by reference } ; print5( "Calling the lambda print5 function." ) ; Integer obj1( 10 ) ; //A copy is created inside the lambda //Copy constructor gets called. //When print6 goes out of scope then //the obj1 gets destroyed. auto print6 = [=](string str1){ cout << str1 << endl ; cout << obj1.x1 << endl ; //We cannot modify obj1 } ; print6( "Calling the lambda print6 function." ) ; //capture all the variables by reference //Copy constructor does not get called. cout << "-------------------" << endl ; auto print7 = [&](string str1){ cout << str1 << endl ; cout << obj1.x1 << endl ; obj1.x1 = 5 ; } ; print7( "Calling the lambda print7 function." ) ; cout << "-------------------" << endl ; //Allowing access to a single variable/object //Another copy of obj1 gets created here. auto print8 = [obj1](string str1){ cout << str1 << endl ; cout << obj1.x1 << endl ; // Other variables are not captured // cout << x1 << endl ; } ; print8( "Calling the lambda print8 function." ) ; auto print9 = [obj1, x1](string str1){ cout << str1 << endl ; cout << obj1.x1 << endl ; // Other variables are not captured cout << x1 << endl ; } ; print9( "Calling the lambda print9 function." ) ; cout << "-------------------" << endl ; //Passing a single variable by reference auto print10 = [&obj1](string str1){ cout << str1 << endl ; //Allowed obj1.x1 = 100 ; cout << obj1.x1 << endl ; } ; print10( "Calling the lambda print10 function." ) ; //Changes are preserved after the invocation cout << obj1.x1 << endl ; cout << "-------------------" << endl ; cout << "-------------------" << endl ; cout << "mix and match example" << endl ; //we can mix and match auto print11 = [&obj1, x1](string str1){ cout << str1 << endl ; cout << x1 << endl ; cout << obj1.x1 << endl ; } ; print11( "Calling the lambda print11 function." ) ; cout << "-------------------" << endl ; auto print12 = [=, &obj1](string str1){ cout << str1 << endl ; cout << x1 << endl ; //x1 = 13 ; compiler error //x1 is only captured by value obj1.x1 = 12 ; cout << obj1.x1 << endl ; } ; print12( "Calling the lambda print12 function." ) ; cout << "-------------------" << endl ; }
In the above file we see the statement:
auto print1 = [](string str1){
cout << str1 << endl ;
} ;
print1( "Calling the lambda print1 function." ) ;
We are using the handy "auto" to define the type for the
lambda expression. With this keyword the system will determine the
type for us. As we will see later the lambda is essentially a
functor object.
We are not trying to access the variables defined in the scope above the
lambda definition.
auto print2 = [](string str1){
cout << str1 << endl ;
// cout << x1 << endl ;
} ;
print2( "Calling the lambda print2 function." ) ;
The capture close is empty ( "[]" ). So if we try
to access "x1" then we get a compiler error.
auto print3 = [=](string str1){
cout << str1 << endl ;
cout << x1 << endl ;
//x1=5 ;
//x1 is read only
} ;
print3( "Calling the lambda print3 function." ) ;
Here we place the equal sign in the capture clause "[=]".
This means we have access to the other variables by value.
We cannot change the values as the "x1" is considered read only.
If we do want to change the value then we need to pass "x1" by
reference.
auto print4 = [&](string str1){
cout << str1 << endl ;
x1=4 ;
} ;
print4( "Calling the lambda print4 function." ) ;
cout << x1 << endl ;
We see in the output that the value of "x1" change was retained.
Calling the lambda print4 function.
4
auto print5 = [x1](string str1){
cout << str1 << endl ;
cout << x1 << endl ;
//cout << x2 << endl ;
//The "x2" is not accessible here.
} ;
print5( "Calling the lambda print5 function." ) ;
We can choose to make only certain variables accessible
to the lambda expression. In the above we are making "x1"
accessible but denying access to any other variables such
as "x2".
Integer obj1( 10 ) ;
//A copy is created inside the lambda
//Copy constructor gets called.
//When print6 goes out of scope then
//the obj1 gets destroyed.
auto print6 = [=](string str1){
cout << str1 << endl ;
cout << obj1.x1 << endl ;
//We cannot modify obj1
} ;
We can also choose to capture objects.In the above we are capturing
the object "obj1" and another copy gets created in the lambda
expression even though the object name stays the same which is "obj1".
We can see that because the "copy constructor" gets called when we
invoke this lambda object.
The same name strategy works fine because we are not allowed
to modify "obj1".
Of course in order to modify "obj1" we need to use the reference
clause.
//capture all the variables by reference
//Copy constructor does not get called.
cout << "-------------------" << endl ;
auto print7 = [&](string str1){
cout << str1 << endl ;
cout << obj1.x1 << endl ;
obj1.x1 = 5 ;
} ;
print7( "Calling the lambda print7 function." ) ;
//Allowing access to a single variable/object
//Another copy of obj1 gets created here.
auto print8 = [obj1](string str1){
cout << str1 << endl ;
cout << obj1.x1 << endl ;
// Other variables are not captured
// cout << x1 << endl ;
} ;
We can choose to allow access to just a single object as the
above snippet shows. We could also place more variables in the
capture clause separated by commas.
auto print9 = [obj1, x1](string str1){
cout << str1 << endl ;
cout << obj1.x1 << endl ;
// Other variables are not captured
cout << x1 << endl ;
} ;
print9( "Calling the lambda print9 function." ) ;
Passing the object by reference.
auto print10 = [&obj1](string str1){
We can choose to make some variable as pass by reference
and others pass by value.
//we can mix and match
auto print11 = [&obj1, x1](string str1){
cout << str1 << endl ;
cout << x1 << endl ;
cout << obj1.x1 << endl ;
} ;
print11( "Calling the lambda print11 function." ) ;
cout << "-------------------" << endl ;
In the below we are stating that "obj1" is passed by reference
and all other parameters are passed by value.
auto print12 = [=, &obj1](string str1){
cout << str1 << endl ;
cout << x1 << endl ;
//x1 = 13 ; compiler error
//x1 is only captured by value
obj1.x1 = 12 ;
cout << obj1.x1 << endl ;
} ;
print12( "Calling the lambda print12 function." ) ;
cout << "-------------------" << endl ;
Exercise
File: lambdas_ex_1.cpp
#include <algorithm> #include <array> #include <string> #include <functional> #include <iostream> using namespace std ; class Person { public: int age ; int id ; Person( int idP , int ageP ) { age = ageP ; id = idP ; } Person(const Person& obj1 ) { cout << "Copy constructor." << endl ; age = obj1.age ; id = obj1.id ; } ~Person() { cout << "Person Destructor." << endl ; } }; void swapM( int& x1, int& x2 ) { int temp ; temp = x1 ; x1 = x2 ; x2 = x1 ; } int main() { int a1 = 10 ; int a2 = 11 ; //TO DO Write a lambda expression to replace swapM //The capture clause [] is empty auto swap1 = swap1(a1, a2 ) ; cout << "a1:" << a1 << " " << "a2:" << a2 << endl ; Person person(1 , 50 ) ; //Pass this object ( "person") by value to a lambda and print // the id and age. { //TO DO Write the code for the lambda in the curly braces //The capture clause is not empty. //Does the copy constructor get called. When does the object //in the lambda get destroyed ? auto print1 = print1() ; } //Pass this object ( "person") by reference to a lambda and print // the id and age. How is this different from the calling by //value above ? //The capture clause is not empty. auto print2 = print2() ; }2) The below program produces a warning. Fix it .
File: lambdas_ex_2.cpp
#include <iostream> int global_var = 10; // A global variable int main() { auto lambda_func = [global_var]() { std::cout << "Value of global_var: " << global_var << std::endl; }; lambda_func(); // Call the lambda return 0; }
Solutions
File: lambdas_ex_1s.cpp
File: lambdas_ex_2s.cpp