Capturing a Reference by Reference in a C++11 Lambda

Capturing a reference by reference in a C++11 lambda

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:

  1. "Scope" is a static, lexical notion in C++, that describes a region of the program source code in which unqualified name lookup associates a particular name with a declaration. It has nothing to do with lifetime. See [basic.scope.declarative]/1.
  2. 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.

How to correctly copy a lambda with a reference capture?

I think I've found the best solution.

The idea is to call the constructor from the copy constructor and then manually set the rest of the members which can be copied trivially:

struct TransformCmp
{
TransformCmp() {}

TransformCmp(const TransformCmp& other)
: TransformCmp() // Makes sure the lambda refs are updated
{
// Trival copy of members
m_dirty = other.m_dirty;
m_position = other.m_position;
}

PropertyDelGetSet<vec3> position =
PropertyDelGetSet<vec3>(
[&]() -> const vec3& { return m_position; },
[&](const vec3& val) { m_position = val; m_dirty = false; });

private:
bool m_dirty = true;
vec3 m_position = vec3(0);
};

This way there's no need to pass around the TransformCmp pointer around in the Property classes which is a lot cleaner. If there was a way to also call the generated copy constructor after overriding it, it would be even cleaner, but this is quite satisfactory to me.

Lambda capture reference variable by reference

Yes, the key issue in capturing an object by reference is the lifetime of the referenced object, not the lifetime of any intervening references used to get it. You can think of a reference as an alias rather than an actual variable. (And in the type system, references are treated differently from regular variables.) The reference aliases the original object, and is independent of other aliases used to alias the object (other than the fact that they alias the same object).

=====EDIT=====

According to the answer given to this SO question (pointed out by dyp), it appears that this may not be entirely clear. Throughout the rest of the language, the concept of a "reference to a reference" doesn't make sense and a reference created from a reference becomes a peer of that reference, but apparently the standard is somewhat ambiguous about this case and lambda-captured references may in some sense be secondary, dependent on the stack frame from which they were captured. (The explicit verbiage the SO answer quotes specifically calls out the referenced entity, which would on the face indicate that this usage is safe as long as the original object lives, but the binding mechanism may implicate the capture chain as being significant.)

I would hope that this is clarified in C++14/17, and I would prefer it to be clarified to guarantee legality for this usage. In particular, I think the C++14/17 ability to capture a variable via an expression will make it more difficult to simply capture a scope via a stack-frame pointer and the most sensible capture mechanism would be to generally capture the specific entities individually. (Perhaps a stack-frame-capture could be permitted if an actual local object is captured by reference, since this would result in UB if the lambda is called outside the scope in any event.)

Until we get some clarification, this may not be portable.

With C++ lambdas, what are the rules for capturing references by reference?

The standard actually mandated it needed to capture the variable, not what it referred to. This was a bug in the standard, and the only case in C++ where this kind of thing could happen.

There is a defect report and suggested resolution (thanks @t.c.) that changed it to capture the referred-to eintity.

In theory, there is a low-cost reference-capture technique that captures the stack pointer and uses offsets known at the point of lambda declaration (plus maybe this) that would use the fact we need only capture a reference by variable not contents. However no compiler I know of used it, and the defect report implies that references you cannot alias into locals/globals cannot be treated this way.

In short, the standard says the wrong thing, but there is no practical problem as no compiler followed the letter of the standard, but rather did the right thing. And future compilers would have to be in violation of the suggested defect resolution to have the bad behaviour.

&' or '=' in lambda capture expression?


If I use [&], m_impl is captured by reference, right? If I use [=] m_impl is captured by value, right?

Neither of those is true. m_impl isn't captured at all. In both cases, the this is captured. But since the thread is a member variable of the object whose this pointer your capturing, this (ha!) is safe.

Use whichever you prefer. Or [this], if you want to be more explicit.

How much does a C++11 lambda capture actually capture?

According to http://en.cppreference.com/w/cpp/language/lambda, the capture list (the part in the square braces) is:

a comma-separated list of zero or more captures, optionally beginning
with a capture-default. Capture list can be passed as follows [...]:

[a,&b] where a is captured by value and b is captured by reference.

[this] captures the this pointer by value

[&] captures all automatic variables odr-used in the body of
the lambda by reference

[=] captures all automatic variables odr-used
in the body of the lambda by value

[] captures nothing

This means that only the automatic (scope-lifetime) variables used in the body of the lambda will be captured.

I can't see why capturing everything with [&] would be more expensive than individual captures, but one advantage of listing out the captures explicitly is that there's no chance of capturing something you didn't expect.

On the other hand, capturing with [=] could prove expensive since it will make copies of everything. Perhaps that's what your coworker was referring to.

Capturing a static variable by reference in a C++11 lambda

Why are you even trying to capture bar? It's static. You don't need to capture it at all. Only automatic variables need capturing. Clang throws a hard error on your code, not just a warning. And if you simply remove the &bar from your lambda capture, then the code works perfectly.

#include <iostream>

int foo() {
static int bar;
return [] () { return bar++; } (); // lambda capturing by reference
}

int main (int argc, char* argv[]) {
std::cout << foo() << std::endl;
std::cout << foo() << std::endl;
std::cout << foo() << std::endl;
return 0;
}

prints

0
1
2

How tu use a C++11 lambda asynchronously when capturing by reference

The lifetime of a temporary constructed for binding to a const reference function parameter is the full-expression containing that function call, so your thread function is referring to a dangling reference.

You should only capture variables into a thread function by reference if you can guarantee that the lifetime of the variable contains the lifetime of the thread, as you have done in the case where function is a local variable in main.

One alternative would be to call join within the full-expression that constructs the temporary:

call(lambda)->join();

Another more general solution would be to capture functor by value in your thread function.

In a lambda, does a by-value capture of a reference copy the underlying object?

By value. Compilable example:

class C
{
public:
C()
{
i = 0;
}

C(const C & source)
{
std::cout << "Copy ctor called\n";
i = source.i;
}

int i;
};

void test(C & c)
{
c.i = 20;

auto lambda = [=]() mutable {

c.i = 55;
};
lambda();

std::cout << c.i << "\n";
}

int main(int argc, char * argv[])
{
C c;
test(c);

getchar();
}

Result:

Copy ctor called
20

I guess, that this paragraph of the C++ standard applies:

5.1.2 Lambda expressions

(...)
14. An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly
captured with a capture that does not include an &. For each entity captured by copy, an unnamed nonstatic
data member is declared in the closure type. The declaration order of these members is unspecified.
The type of such a data member is the type of the corresponding captured entity if the entity is not a
reference to an object, or the referenced type otherwise. [ Note: If the captured entity is a reference to a
function, the corresponding data member is also a reference to a function. —end note]

That actually makes sense - if local variables are passed by value and parameter passed by reference "acts" as a local variable in function, why would it be passed by reference instead of value?



Related Topics



Leave a reply



Submit