Reputation: 527
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
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
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
[=]
capturesthis
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
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