Compiler variance: type of `this` when value-captured in mutable lambda defined in a const member function

Viewed 66

The following program

#include <type_traits>

struct S {
  void f() const {
    (void)[*this]() mutable {
      static_assert(
          std::is_same_v<decltype(this), S *>);  // Fails with GCC & MSVC
      static_assert(
          std::is_same_v<decltype(this), const S *>);  // Fails with Clang
      (void)this;
    }
    ();
  }
};

int main() {}

contains a mutable lambda which captures *this by value ([*this]), and which does so in a const-qualified member function. The program is rejected by GCC due to #1, and rejected by Clang due to #2 (and conversely accepted):

  • GCC(1) and MSVC thinks decltype(this) is const S *
  • Clang(1) thinks decltype(this) is S *

(1) Various compiler versions for C++17 and C++20

DEMO.

Which compiler is right here?

1 Answers

GCC/MSVC are correct, Clang is reasonable but incorrect

[expr.prim.lambda.closure[/12:

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.

means [class.this]/1 applies:

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”.

GCC/MSVC and Clang may be applying [class.this]/1, particularly regarding the cv-qualifier of the member function, to either of

  1. the cv-qualification of the member function in which the lambda is defined, S::f() in OP's example, which is const-qualified, or
  2. the cv-qualification of the function call operator of the lambda, which is not const-qualified (due to mutable),

particularly GCC and MSVC choosing interpretation 1, whereas Clang chooses interpretation 2. We may note that if we slightly tweak OP's example to capture the lambda by capturing *this by reference, [this], all compilers agree that decltype(this) in the lambda is const S * (which makes sense).

Based on the standard passages above, particularly [expr.prim.lambda.closure]/12, I would argue that Clang is wrong to let this be non-const-qualified when *this has been value-captured by a mutable lambda from within a const-qualified method. Even if it could make sense to base constness for a value-copied *this on the constness of the function call operator of the lambda's closure type, the standard does not support it.

We may finally note that we see the same behaviour if using overload resolution instead of decltype (i.e. comparing this in our outside of an unevaluated context), namely GCC and MSVC preserving constness of the copy-captured *this, where Clang does not.

Bug reports?

I have not been able to find any Clang bug reports for this, and have hence filed:

Related