When a lambda need the local variables from outer block, we need to list them in the capture clause of lambda. For example:
int main(int argc, char const *argv[])
{
int value{10};
auto print{
[value]
{
std::cout << value << '\n';
}};
print();
return 0;
}There are certain kind of objects that lambda can access without capturing them:
- Objects with static duration such global and static variables.
- Constexpr variables either explicitly defined or are implicit.
For example:
int main(int argc, char const *argv[])
{
static int value{10};
auto print{
[]
{
std::cout << value << '\n';
}};
print();
return 0;
}How does lambda captures work?
Lambdas do not use actual data variables from outer scope, instead those variable get cloned and used by the same name in the lambda.
If we go in more depth, lambdas look like function, but they are not. They are objects which can be called (called functors) because they overload operator(). When compiler encounters lambda definition, it creates a custom object definition of the lambda such that whatever variables that we provide in capture clause, become the data members of the object.
In run time, when lambda definition is encountered, the lambda object instance is created with the data members initialized with the variable from the outer scope.
Capture by value
In the above example, we are capturing variables by value. It means, they will be copied to the capture variable name for the lambda. So, lambda will have its own independent copy. By default, these captured variables are treated as const. For example:
#include <iostream>
int main(int argc, char const *argv[])
{
int value{0};
auto counter{
[value]()
{
return ++value;
}};
std::cout << counter() << '\n';
std::cout << counter() << '\n';
std::cout << counter() << '\n';
std::cout << value << '\n';
return 0;
}When compiling this program, we would get compilation error saying value is read only.
We can fix this error by making captured variables as mutable.
#include <iostream>
int main(int argc, char const *argv[])
{
int value{0};
auto counter{
[value]() mutable
{
return ++value;
}};
std::cout << counter() << '\n';
std::cout << counter() << '\n';
std::cout << counter() << '\n';
std::cout << value << '\n';
return 0;
}When we run this program, we should get something like below:
1
2
3
0
Please note that each counter call using the same lambda object instance, so they will use the same captured value and so value is persisted across the calls.
Although, we are mutating the variable inside the lambda, the outer scope is left intact because lambdas is using the clone variable.
Capture by reference
It is also possible to capture variables by reference as shown below:
#include <iostream>
int main(int argc, char const *argv[])
{
int value{0};
auto counter{
[&value]()
{
return ++value;
}};
std::cout << counter() << '\n';
std::cout << counter() << '\n';
std::cout << counter() << '\n';
std::cout << value << '\n';
return 0;
}We need to prepend the captured variable with & and that’s all. With this, we are using outer scope variable inside the lambda and updating it inside would change the original variable.
It would give result as:
1
2
3
3
Multiple captures
If lambda is accessing multiple variables, we need to capture all of them in the capture clause:
int main(int argc, char const *argv[])
{
int by_value{10};
int by_ref{12};
auto print{
[by_value, &by_ref]
{
std::cout << by_value << '\n';
std::cout << by_ref << '\n';
}};
return 0;
}