TL;DR: The code in the question is not guaranteed by the Standard, and there are reasonable implementations of lambdas which cause it to break. Assume it is non-portable and instead use
std::function<void()> make_function(int& x)
{
const auto px = &x;
return [/* = */ px]{ std::cout << *px << std::endl; };
}
Beginning in C++14, you can do away with explicit use of a pointer using an initialized capture, which forces a new reference variable to be created for the lambda, instead of reusing the one in the enclosing scope:
std::function<void()> make_function(int& x)
{
return [&x = x]{ std::cout << x << std::endl; };
}
On first glance, it seems that should be safe, but the wording of the Standard causes a bit of a problem:
A lambda-expression whose smallest enclosing scope is a block scope (3.3.3) is a local lambda expression; any other lambda-expression shall not have a capture-default or simple-capture in its lambda-introducer. The reaching scope of a local lambda expression is the set of enclosing scopes up to and including the
innermost enclosing function and its parameters.
...
All such implicitly captured entities shall be declared within the reaching scope of the lambda expression.
...
[ Note: If an entity is implicitly or explicitly captured by reference, invoking the function call operator of the corresponding lambda-expression after the lifetime of the entity has ended is likely to result in undefined behavior. — end note ]
What we expect to happen is that x
, as used inside make_function
, refers to i
in main()
(since that is what references do), and the entity i
is captured by reference. Since that entity still lives at the time of the lambda call, everything is good.
But! "implicitly captured entities" must be "within the reaching scope of the lambda expression", and i
in main()
is not in the reaching scope. :( Unless the parameter x
counts as "declared within the reaching scope" even though the entity i
itself is outside the reaching scope.
What this sounds like is that, unlike any other place in C++, a reference-to-reference is created, and the lifetime of a reference has meaning.
Definitely something I would like to see the Standard clarify.
In the meantime, the variant shown in the TL;DR section is definitely safe because the pointer is captured by value (stored inside the lambda object itself), and it is a valid pointer to an object which lasts through the call of the lambda. I would also expect that capturing by reference actually ends up storing a pointer anyway, so there should be no runtime penalty for doing this.
On closer inspection, we also imagine that it could break. Remember that on x86, in the final machine code, both local variables and function parameters are accessed using EBP-relative addressing. Parameters have a positive offset, while locals are negative. (Other architectures have different register names but many work in the same way.) Anyway, this means that capture-by-reference can be implemented by capturing only the value of EBP. Then locals and parameters alike can again be found via relative addressing. And in fact I believe I've heard of lambda implementations (in languages which had lambdas long before C++) doing exactly this: capturing the "stack frame" where the lambda was defined.
What this implies is that when make_function
returns and its stack frame goes away, so does all ability to access locals AND parameters, even those which are references.
And the Standard contains the following rule, likely specifically to enable this approach:
It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference.
Conclusion: The code in the question is not guaranteed by the Standard, and there are reasonable implementations of lambdas which cause it to break. Assume it is non-portable.
x
is:std::function<void()> make_function(int& x) { auto px = &x; return [=](){ std::cout << *px << std::endl; }; }
– Throes