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*thisAs 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
thisis a prvalue whose value is a pointer to the object for which the function is called. The type ofthisin a member function whose type has a cv-qualifier-seq cv and whose class isXis “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
thisand 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
thisin a member function whose type has a cv-qualifier-seq cv and whose class isXis “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