Consider this:
#include <functional>
#include <iostream>
std::function<void()> make_function(int& x) {
return [&]{ std::cout << x << std::endl; };
}
int main() {
int i = 3;
auto f = make_function(i);
i = 5;
f();
}
Is this program guaranteed to output 5
without invoking undefined behavior?
I understand how it works if I capture x
by value ([=]
), but I am not sure if I am invoking undefined behavior by capturing it by reference. Could it be that I will end up with a dangling reference after make_function
returns, or is the captured reference guaranteed to work as long as the originally referenced object is still there?
Looking for definitive standards-based answers here :) It works well enough in practice so far ;)
The code is guaranteed to work.
Before we delve into the standards wording: it's the C++ committee's intent that this code works. However, the wording as it stands was believed to be insufficiently clear on this (and indeed, bugfixes made to the standard post-C++14 broke the delicate arrangement that made it work), so CWG issue 2011 was raised to clarify matters, and is making its way through the committee now. As far as I know, no implementation gets this wrong.
I'd like to clarify a couple of things, because Ben Voigt's answer contains some factual errors that are creating some confusion:
The "reaching scope" rules for lambdas are, likewise, a syntactic property that determine when capture is permitted. For example:
void f(int n) {
struct A {
void g() { // reaching scope of lambda starts here
[&] { int k = n; };
// ...
n
is in scope here, but the reaching scope of the lambda does not include it, so it cannot be captured. Put another way, the reaching scope of the lambda is how far "up" it can reach and capture variables -- it can reach up to the enclosing (non-lambda) function and its parameters, but it can't reach outside that and capture declarations that appear outside.
So the notion of "reaching scope" is irrelevant to this question. The entity being captured is make_function
's parameter x
, which is within the reaching scope of the lambda.
OK, so let's look at the standard's wording on this issue. Per [expr.prim.lambda]/17, only id-expressions referring to entities captured by copy are transformed into a member access on the lambda closure type; id-expressions referring to entities captured by reference are left alone, and still denote the same entity they would have denoted in the enclosing scope.
This immediately seems bad: the reference x
's lifetime has ended, so how can we refer to it? Well, it turns out that there is almost (see below) no way to refer to a reference outside its lifetime (you can either see a declaration of it, in which case it's in scope and thus presumably OK to use, or it's a class member, in which case the class itself must be within its lifetime for the member access expression to be valid). As a result, the standard did not have any prohibitions on using a reference outside its lifetime until very recently.
The lambda wording took advantage of the fact that there is no penalty for using a reference outside its lifetime, and so didn't need to give any explicit rules for what access to an entity captured by reference means -- it just means you use that entity; if it's a reference, the name denotes its initializer. And that's how this was guaranteed to work up until very recently (including in C++11 and C++14).
However, it's not quite true that you can't mention a reference outside its lifetime; in particular, you can reference it from within its own initializer, from the initializer of a class member earlier than the reference, or if it is a namespace-scope variable and you access it from another global that is initialized before it is. CWG issue 2012 was introduced to fix that oversight, but it inadvertantly broke the specification for lambda capture by reference of references. We should get this regression fixed before C++17 ships; I've filed a National Body comment to make sure it's suitably prioritized.