Lambda in C++ consist of expressions and statements (popularly called Lambda expressions). These expressions cause compilers to generate internal classes. Such internal classes are called Closure Class. Finally, from the closure class, C++ compiler instantiate objects during runtime. These runtime-objects are similar to ordinary objects but they encapsulate the lambda functionality. The lambda expressions, become part of source code of the closure classes.
The terms, lambda, closure, closure-classes generally are used interchangeably.
Example of simplest lambda is:
auto lambda = [](){};
Other example:
int x, y;
auto c1 = [x](int y) { return x * y > 55; };
In the above statement, the statement on the right-hand side is Lambda expression. Finally, c1 is the runtime object. The program will use this c1 to get the functionality defined by lambda.
The lambda in C++ contains 2 capture modes, by-reference and by-value capture mode.
By-Reference Capture Mode
By-reference capture mode will cause a closure class to create the member variables of reference type. These references point to local variables which are present in the same scope as lambda. To enable creation of by-reference, the square brackets use ampersand [&].
The following example forms a basic lambda with by-reference mode. The variable “tmp” inside lambda becomes reference to local variable “tmp” in initialize function.
#include <iostream> //main header
#include <functional> //for function ptr
using namespace std; //namespace
std::function<void()> fp;
void initfunda()
{
std::string tmp = "temporary string";
cout << "Address in function = " << &tmp << endl;
fp = [&](){ cout<<"Address in lambda = " <<&tmp<<endl; };
fp(); //call lambda
}
int main()
{
initfunda();
return 0;
}
The output from compiler shows variable “tmp” has same address both outside and inside the class.
There is another way to enable by-reference, i.e. by explicit reference of “tmp”. The following code snippet uses explicit reference.
fp = [&tmp](){ cout<< "Address in lambda = "<<&tmp<<endl; };
By-value capture mode
By-value capture mode will cause a closure class to create copies of local variables. Therefore, the local variables which are in the same scope just copies. To enable by-value, the square brackets use equal-to sign [=].
The following example forms a basic lambda with by-value mode. The variable “tmp” inside lambda becomes copy of local variable “tmp” in initialize function.
#include <iostream> //main header
#include <functional>//for function ptr
using namespace std; //namespace
std::function<void()> fp;
void initfunda()
{
std::string tmp = "temporary string";
cout << "Address in function = " << &tmp << endl;
fp = [=](){ cout <<"Address in lambda = "<<&tmp<<endl; };
fp(); //call lambda
}
int main()
{
initfunda();
return 0;
}
The output of program shows that both the “tmp” variables have different addresses.
Just like explicit by-reference, there is similar format for explicit by value.
fp = [tmp](){ cout << "Address in lambda = "<<&tmp<< endl;};
Capture by-value with class objects
In case, the local variables are objects, then for doing by-value capture, compiler shall call copy-constructor to create copy of objects. The following example illustrates this.
#include <iostream> //main header
#include <functional>//for function ptr
using namespace std; //namespace
class MainFunda
{
public:
MainFunda() {
cout << "MainFunda()\n";
}
MainFunda(const MainFunda&) {
cout << "MainFunda(const MainFunda&)\n";
}
~MainFunda() {
cout << "~MainFunda()\n";
}
};
int main()
{
MainFunda a1;
auto fp = [a1](){ }; //Invoke copy constructor
return 0;
}
The output shows copy constructor called for copying object a1 in lambda closure.
Capture do not happen for static variables in local scope
The following examples shows that “tmp” is now static variable. Therefore, no copy of “tmp” happens inside closure class. The lambda expression shall be calling the actual static variable even with by-value capture mode.
#include <iostream> //main header
#include <functional> //for function ptr
using namespace std; //namespace
std::function<void()> fp;
void initfunda()
{
static std::string tmp = "temporary string";
cout << "Address in function = " << &tmp << endl;
fp = [=](){ cout <<"Address in lambda = "<<&tmp<<endl; };
fp(); //call lambda
}
int main()
{
initfunda();
return 0;
}
The output shows that address of “tmp” inside lambda is same as address of static variable “tmp”.
When capture by-reference and reference variable goes out of scope
The capture by-reference mode should be used very carefully. This is because, there is a possibility that lifetime of lambda shall out-live the scope of local variables. In following example, “fp” is global variable so the lambda will live till the program is running. However, local variable “tmp” will be destroyed the moment initialize function returns. Hence, reference to “tmp” will become dead inside lambda. Thereafter, any use of “tmp” will have undefined behavior.
#include <iostream> //main header
#include <functional>//for function ptr
using namespace std; //namespace
std::function<void()> fp;
void initfunda()
{
std::string tmp = "temporary string";
fp = [&](){ cout << "Executing Lambda : " << tmp << endl; };
fp(); //Valid Call
}
int main()
{
initfunda();
fp(); //Invalid Call
return 0;
}
The output shows that when main function calls lambda, then it prints garbage value for “tmp”.
Use static variable or capture by-copy to solve the problem
The following problem solves the problem of by-reference by making the tmp variable as static. Therefore, “tmp” will live till this program is running.
#include <iostream> //main header
#include <functional>//for function ptr
using namespace std; //namespace
std::function<void()> fp;
void initfunda()
{
static std::string tmp = "temporary string";
fp = [&](){ cout <<"Executing Lambda : "<<tmp<< endl; };
fp(); //Valid Call
}
int main()
{
initfunda();
fp(); //Valid Call
return 0;
}
Output:
Capture by-reference of non-automatic variables
Capture should happen only for local variables. When the program forcefully passes a global variable for capture, then compiler shall give error.
#include <iostream> //main header
#include <functional>//for function ptr
using namespace std; //namespace
std::function<void()> fp;
std::string tmp = "temporary string"; //global
void initfunda()
{
fp = [&tmp](){ cout<<"Executing Lambda : "<<tmp<<endl; };
fp(); //call lambda
}
int main()
{
initfunda();
fp(); //call lambda
return 0;
}
However, Some newer compiler may generate warning instead of error.
Same rule applies on by-value capture.
The following code snippet uses tmp to enable capture by-value.
fp = [tmp](){ cout << "Executing Lambda : "<< tmp << endl; };
Output
Move object inside Lambda
In C++14, there is better capture mode which has more flexibility. This is generalized or init capture mode. The following program uses generalized capture mode to move an object to lambda. This mode can achieve what by-reference and by-value mode cannot.
Syntax:
[<Name of Data member in closure class> = <Expression>]
#include <iostream> //main header
#include <functional>//for function ptr
using namespace std; //namespace
std::function<void()> fp;
std::string tmp = "temporary string";
//works for non-local too
void initfunda()
{
fp = [tmp=std::move(tmp)]()
{
cout << "Executing Lambda : " << tmp << endl;
};
fp(); //call lambda
}
int main()
{
cout << "tmp = " << tmp << endl;
initfunda();
cout << "tmp = " << tmp << endl;
fp();
return 0;
}
The output shows that when program moves “tmp” inside lambda, then it becomes available only on invocation of lambda and not outside it.