Lambda Functions
This text describes and elaborates on lambda functions, specifically in C++.
What is a lambda function
It is just syntactic sugar for an unnamed class.
auto f = [x](int y) { return x + y; };
is roughly (internally) :
struct __Lambda {
int x; // captured variable stored as member
__Lambda(int x_) : x(x_) {}
int operator()(int y) const {
return x + y;
}
};
auto f = __Lambda(x);
So a lambda is just an object of a custom unnamed class.
Calling a lambda calls an internal operator.
The lifetime of the lambda itself follows normal rules of C++ object lifetimes.
If it goes out of scope, it is destroyed.
Capturing variables
Capture by value
int x = 10;
auto f = [x]() { return x; };
Here, x is copied by value.
The object of lambda, gets the copy of the then value of x, and the lifetime is same as the lifetime of the lambda.
The value ofx is, by default, const inside the lambda. x cannot be changed inside the function.
For it to be non-const, we need mutable lambdas.
[x]() mutable { x++; } // modifies the COPY
Note that this modifies only the internal copy of the lambda, not the underlying value it picked up.
Capture by reference
int x = 10;
auto f = [&x]() { return x; };
Lambda stores a reference(pointer) to the original memory of x.
Lifetime of variable inside the variable is the same as lifetime of variable outside it
You must guarantee that the variable outlives the lambda, otherwise calling the lambda after the variable has gone out of scope is UB.
Default captures
| Syntax | Meaning |
|---|---|
[=] |
capture everything by value |
[&] |
capture everything by reference |
auto f = [=]() { return x + y; }; // copies both
auto g = [&]() { return x + y; }; // references both
You can override:
[=, &x] // mostly value, x by reference
[&, x] // mostly reference, x by value
Examples and common bugs
Returning Lambdas
The lambda returned by any function will try to also return the variables it holds, just think of everything in the "Object" Model.
If the variable ceases to exist before the lambda can be called (especially if captured by reference), then UB.
Safe:
auto make_lambda() {
int x = 10;
return [x]() { return x; }; // OK (copied)
}
Dangerous:
auto make_lambda() {
int x = 10;
return [&x]() { return x; }; // UB
}
Since when the function call returns, x ceases to exist, the reference inside the lambda is invalid.
Capturing this from inside class
class A {
public:
int x = 10;
auto f() {
return [this]() { return x; };
}
};
This returns a copy of the pointer. Note that it points to the same underlying memory as that of the original object.
If we store the lambda and try to call it when the original object has ceased to exist, we get UB.
Safer alternative : Copying object
return [*this]() { return x; };
This actually copies the object (we de-referenced this, trying to pass object itself as parameter, by value)
auto ptr = std::make_unique<int>(10);
auto f = [p = std::move(ptr)]() {return *p;};
This transfers ownership of ptr to p.
The ptr itself becomes null after the declaration of f
After this declaration of lambda with a move-only parameter, the entire lambda becomes move-only. This means it can no longer be copied.
{cpp} auto g = f fails immediately.
{cpp}auto g = std::move(f); works just fine.
Calling a lambda after it has been moved from is again an UB.
Returning a move-only lambda is fine, because we throw the same object around without creating copies.
A single move-only parameter makes the entire lambda move only, doesn't matter if there are other copyable parameters.
We can move out the lambda, if the lambda is of the type
{cpp}auto f = [p = std::make_unique<int>(10)]() mutable {return std::move(p);};
Essentially, we return the moved thing, so if we do
{cpp}unique_ptr<int> x = f(); // ownership transferred
And then
{cpp}unique_ptr<int> y = f(); This will return a nullptr, since the move-only object we are trying to move has already been moved
Lambdas can have static variables inside them that persist throughout the lifetime of the lambda (same as class)
Lambdas that capture by reference, if copied (the lambda itself is copied), the reference is also copied, the new lambda gets a copy of the reference. Same underlying object.