Immanuel Kant
Immanuel Kant

Reputation: 527

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.

Upvotes: 2

Views: 1090

Answers (3)

catnip
catnip

Reputation: 25388

I think people are over-thinking this. If you think of this as a pointer (which I have always done, and which of course it is), then everything naturally falls into place. And quite honestly, I don't really know what problem the PR you cite is trying to address. Personally, I don't think there is one.

So (apart from the deprecation warning in C++20), there is no practical difference, in terms of capturing this, between:

[=]
[&]

and:

[this]

In all cases, they capture the pointer, and that can be used inside the lambda (implicitly or explicitly) to manipulate the object in question.

Now it's true that, in principle, [&] captures this (i.e. the pointer) by reference, but since you can't assign to it that makes no practical difference and I would expect the compiler to optimise the implied dereference away. I would also agree that capturing this explicitly is better style, but that's true for any variable you want to capture, IMO. And I would strongly disagree with the notion that implicitly capturing this via [&] is in any way more legitimate than capturing it via [=], but that's what they seem to have gone for.

One other minor detail is that when you capture a variable by value (by whatever method) it is, as others have pointed out, implicitly const in the body of the lambda. That's to stop you making silly mistakes. But, in the case of this, it's the pointer that's const (which, in effect, this always is anyway), not the object it's pointing to. So you can do whatever you like to that object, just party on down.

As for capturing *this (which you have to do explicitly, and always have done), [*this] will pass a copy of the object itself into the lambda. Like anything else captured by value, this will (implicitly) be const so declare your lambda as mutable if that presents a problem.

(Thank you Ted for pointing out my error).

And remember to capture responsibly, folks. The standards committee is watching you.

Upvotes: 3

HolyBlackCat
HolyBlackCat

Reputation: 96053

Thinking in terms of "capturing this" can be confusing. Think in terms of "capturing *this" (the current class instance).

[&] and [=] have exactly the same meaning with respect to *this: both capture *this by reference.

This makes sense for [&]. But this is weird for [=], since it captures by value otherwise. This is why C++20 deprecates the capture of this by [=], forcing you to manually spell either [this] (by reference) or [*this] (by value).


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.

Nope, you misunderstood the quote. Both [=] and [&] capture *this by reference (aka this by value), there is no ambiguity.

As I see it, the quote just points out the inconsistency of [=] capturing *this by reference.

I can't even change my capture into [=, *this] or [&, *this] as compiler will say: error: read-only variable is not assignable

If you want to modify any by-value capture, the lambda needs to be mutable.

Upvotes: 1

Ted Lyngmo
Ted Lyngmo

Reputation: 117288

  • [=], [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
};

Upvotes: 7

Related Questions