96
votes

I understand the correct way to capture this (to modify object properties) in a lambda is as follows:

auto f = [this] () { /* ... */ };

But I'm curious as to the following peculiarity I've seen:

class C {
    public:
        void foo() {
            // auto f = [] () { // this not captured
            auto f = [&] () { // why does this work?
            // auto f = [&this] () { // Expected ',' before 'this'
            // auto f = [this] () { // works as expected
                x = 5;
            };
            f();
        }

    private:
        int x;
};

The oddity that I am confused by (and would like answered) is why the following works:

auto f = [&] () { /* ... */ }; // capture everything by reference

And why I cannot explicitly capture this by reference:

auto f = [&this] () { /* ... */ }; // a compiler error as seen above.
2
Why would you want to? In terms of things for which a reference to a pointer might ever conceivably be useful: this can't be changed, it isn't large enough to make a reference faster... and anyway, it doesn't actually exist, so it has no real lifetime, meaning any reference to it would be dangling by definition. this is a prvalue, not an lvalue.underscore_d

2 Answers

116
votes

The reason [&this] doesn't work is because it is a syntax error. Each comma-seperated parameter in the lambda-introducer is a capture:

capture:
    identifier
    & identifier
    this

You can see that &this isn't allowed syntactically. The reason it isn't allowed is because you would never want to capture this by reference, as it is a small const pointer. You would only ever want to pass it by value - so the language just doesn't support capturing this by reference.

To capture this explicitly you can use [this] as the lambda-introducer.

The first capture can be a capture-default which is:

capture-default:
    &
    =

This means capture automatically whatever I use, by reference (&) or by value (=) respectively - however the treatment of this is special - in both cases it is captured by value for the reasons given previously (even with a default capture of &, which usually means capture by reference).

5.1.2.7/8:

For purposes of name lookup (3.4), determining the type and value of this (9.3.2) and transforming id- expressions referring to non-static class members into class member access expressions using (*this) (9.3.1), the compound-statement [OF THE LAMBDA] is considered in the context of the lambda-expression.

So the lambda acts as if it is part of the enclosing member function when using member names (like in your example the use of the name x), so it will generate "implicit usages" of this just like a member function does.

If a lambda-capture includes a capture-default that is &, the identifiers in the lambda-capture shall not be preceded by &. If a lambda-capture includes a capture-default that is =, the lambda-capture shall not contain this and each identifier it contains shall be preceded by &. An identifier or this shall not appear more than once in a lambda-capture.

So you can use [this], [&], [=] or [&,this] as a lambda-introducer to capture the this pointer by value.

However [&this] and [=, this] are ill-formed. In the last case gcc forgivingly warns for [=,this] that explicit by-copy capture of ‘this’ redundant with by-copy capture default rather than errors.

7
votes

Because standard doesn't have &this in Captures lists:

N4713 8.4.5.2 Captures:

lambda-capture:
    capture-default
    capture-list
    capture-default, capture-list

capture-default:
    &
    =
capture-list:
    capture...opt
    capture-list, capture...opt
capture:
    simple-capture
    init-capture
simple-capture:
    identifier
    &identifier
    this
    * this
init-capture:
    identifier initializer
    &identifier initializer
  1. For the purposes of lambda capture, an expression potentially references local entities as follows:

    7.3 A this expression potentially references *this.

So, standard guarantees this and *this is valid, and &this is invalid. Also, capturing this means capturing *this(which is a lvalue, the object itself) by reference, rather than capturing this pointer by value!