Search code examples
c++lambdacopythiscapture

C++11/14/17 Lambda reference capture [&] doesn't copy [*this]


Refer to this thread: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0806r2.html

It says:

In other words, one default capture ([&]) captures *this in the way that would be redundant when spelled out, but the other capture ([=]) captures it in the non-redundant way.

Which says that pre c++17, the [=] captures this as value, and [&] will capture [*this], which is ambiguous. So I had a quick test, to see if [&] captures [*this] by default.

My test code trys to see if [&] defaultly captures *this, then copy ctor should be called, and any change to its value won't affect original object, as it is a copy.

#include<iostream>
using namespace std;
class M{
    int mI;
public:
    M() : mI(3) { cout << "ctor\n"; }
    ~M() { cout << "dtor\n"; }
    M(const M& m) {
        if (this != &m) {
            cout << "copy ctor\n";
            mI = m.mI;
        }
    }
    M& operator=(const M& m) {
        if (this != &m) {
            cout << "operator =\n";
            mI = m.mI;
        }
        return *this;
    }

    void CaptureByValue() {
        auto f1 = [=] () { // capture this
            cout << mI << '\n';
            ++(this->mI);
        };
        f1();
        cout << mI << '\n';
    }
    void CaptureByReference() {
        auto f1 = [&] () { // capture *this
            cout << mI << '\n';
            ++(this->mI);
        };
        f1();
        cout << mI << '\n';
    }
};

int main() {
    {
        M obj1;
        obj1.CaptureByValue();
    }
    cout << "------------\n";
    {
        M obj2;
        obj2.CaptureByReference();
    }
    return 0;
}

Compile and run it with:

clang++ LambdaCapture.cpp -std=c++11 && ./a.out
clang++ LambdaCapture.cpp -std=c++14 && ./a.out
clang++ LambdaCapture.cpp -std=c++17 && ./a.out

All cases print:

ctor
3
4
dtor
------------
ctor
3
4
dtor

My questions:

(1) The result is out of my expectation: no copy ctor is called by CaptureByReference and the value being changed affected original this object. The test code didn't promoted the lambda syntax change in cpp17.

(2) I can't even change my capture into [=, *this] or [&, *this] as compiler will say:

LambdaCapture.cpp:25:13: error: read-only variable is not assignable
            ++(this->mI);
            ^ ~~~~~~~~~~

Very strange, how came out a read-only variable here, as to this pointer.


Appreciate your explanations.


Solution

    • [=], [this], [=, this] and [&, this] all captures this by value. That is, it copies the value of the pointer that is this.
    • [&] captures *this by reference. That is, this in the lambda is a pointer to *this outside the lambda.

    The effect of the above versions with regards to this in the lambda will therefore be the same.

    • [=, *this] copies all elements captured and also makes a copy of *this - not the this pointer.
    • [&, *this] makes a reference to all elements captured but makes a copy of *this.
    LambdaCapture.cpp:25:13: error: read-only variable is not assignable
                ++(this->mI);
                ^ ~~~~~~~~~~
    

    That's because lambdas are const by default. You need to make them mutable in order to be able to change them.

    auto f1 = [&, *this]() mutable { // made mutable
        cout << mI << '\n';
        ++(this->mI);                // now ok
    };