Everyone knows that this
object pointer in C++ cannot be changed in its methods. But what comes to mutable lambdas, where this
is captured, some current compilers give such possibility. Consider this code:
struct A {
void foo() {
//this = nullptr; //error everywhere
(void) [p = this]() mutable {
p = nullptr; //#1: ok everywhere
(void)p;
};
(void) [this]() mutable {
this = nullptr; //#2: ok in MSVC only
};
}
};
In the first lambda this
is captured and given a new name p
. Here all compilers permit to change the value of p
. In the second lambda this
is captured by its own name, and only MSVC allows the programmer to change its value. Demo: https://gcc.godbolt.org/z/x5P81TT4r
I believe MSVC behaves incorrectly in the second case (though it looks like a nice language extension). Could anyone find the right wording from the standard (the search is not easy since the word this
is mentioned there 2800+ times)?
this
(void) [p = this]() mutable { p = nullptr; //#1: ok everywhere (void)p; };
This uses init-capture to capture the this
pointer by value, as per [expr.prim.lambda.capture]/6, meanings it's a copy of the this
pointer. In contexts where this
is const
-qualified, the copy can naturally not be used to changed this
(even if the lambda is mutable; compare with 'point to const'), but as the lambda is mutable, the pointer (copy) can be used to point to something different, e.g. nullptr
.
struct S {
void f() const {
(void) [p = this]() mutable {
p->value++; // ill-formed: 'this' is pointer to const, meaning
// 'p' is pointer to const.
p = nullptr; // OK: 'p' is not const pointer
(void)p;
};
}
void f() {
(void) [p = this]() mutable {
p->value++; // OK: 'this' is pointer to non-const, meaning
// 'p' is pointer to non-const.
p = nullptr; // OK: 'p' is not const pointer
(void)p;
};
}
int value{};
};
this
:(void) [this]() mutable { this = nullptr; //#2: ok in MSVC only };
As per [expr.prim.lambda.capture], ignoring the case of capture-default:s:
&
identifier ...opt
this
*this
As per [expr.prim.lambda.capture]/10 [emphasis mine]:
An entity is captured by copy if
(10.1) it is implicitly captured, the capture-default is
=
, and the captured entity is not*this
, or(10.2) it is explicitly captured with a capture that is not of the form
this
,&
identifier, or&
identifier initializer.
Only the simple-capture form *this
allows explicitly capturing the *this
object by copy. The simple capture this
, however, captures the *this
object(+) by reference, as per [expr.prim.lambda.capture]/12:
(+) The simple-capture:s this
and *this
both denote the local entity *this
, as per [expr.prim.lambda.capture]/4.
An entity is captured by reference if it is implicitly or explicitly captured but not captured by copy. It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference. [...]
Thus:
struct S {
void f() const {
(void) [this]() mutable {
// '*this' explicitly-captured by-reference
this->value++; // ill-formed: 'this' is pointer to const
this = nullptr; // #2 ill-formed: 'this' is not a modifyable lvalue
};
}
void f() {
(void) [this]() mutable {
// '*this' explicitly-captured by-reference
this->value++; // OK: 'this' is pointer to non-const
this = nullptr; // #2 ill-formed: 'this' is not a modifyable lvalue
};
}
int value{};
};
As per [class.this]/1, this
is not a modifyable lvalue, which is why #2
is ill-formed:
In the body of a non-static ([class.mfct]) member function, the keyword
this
is a prvalue whose value is a pointer to the object for which the function is called. The type ofthis
in a member function whose type has a cv-qualifier-seq cv and whose class isX
is “pointer to cv X”. [...]
Which, as per [expr.prim.lambda.closure]/12, applies also to when this
is used in lambdas:
The lambda-expression's compound-statement yields the function-body ([dcl.fct.def]) of the function call operator, but for purposes of name lookup, determining the type and value of
this
and transforming id-expressions referring to non-static class members into class member access expressions using (*this) ([class.mfct.non-static]), the compound-statement is considered in the context of the lambda-expression.
MSVC is this incorrect to accept your snippet (accepts-invalid).
And indeed, in the following example (demo):
#include <iostream>
struct S;
void g(S *& ) { std::cout << "lvalue pointer to S"; }
void g(S *&& ) { std::cout << "rvalue pointer to S"; }
struct S {
void f() {
auto l = [this]() { g(this); };
l();
}
};
int main() {
S s{};
s.f();
}
We expect the second g
overload to be a better match, as this
is a prvalue. However, whilst GCC and Clang behaves as expected:
// GCC & Clang: rvalue pointer to S
MSVC fails to even compile the program:
// MSVC: error, no viable overload; arg list is '(S *const )'
Which is in violation of [class.this]/1, as:
[...] The type of
this
in a member function whose type has a cv-qualifier-seq cv and whose class isX
is “pointer to cv X” [...]
... and not "const pointer to cv X" (constness on a prvalue would be weird, to begin with).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With