Init-captures of 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{};
};
Simple-captures of this:
(void) [this]() mutable {
this = nullptr; //#2: ok in MSVC only
};
As per [expr.prim.lambda.capture], ignoring the case of capture-default:s:
- A capture-list contains a capture
- A capture is either a simple-capture or an init-capture; we ignore the latter case as it was covered above
- A simply-capture has one of the following forms:
- identifier ...opt
&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 of this in a member function whose type has a cv-qualifier-seq cv and whose class is X 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 is X is “pointer to cv X” [...]
... and not "const pointer to cv X" (constness on a prvalue would be weird, to begin with).