Search code examples
c++gcclambdac++17this

How does GCC handle such a nested lambda where 'this' is getting captured


I have this piece of code with nested lambdas and both sort of capturing 'this':

class MyClass2 {
public:
    int value;

    MyClass2(int initialValue) : value(initialValue) {}

    void executeLambda() {
        auto outerLambda = [this]()  {
            value++;
            auto innerLambda = [*this]() mutable {
                value++;
            };
            innerLambda();
        }; 
        outerLambda();
    }
};

cppinsights.io shows the compiler transformation as below:

class MyClass2  /* size: 4, align: 4 */
{
  
  public: 
  int value;                      /* offset: 0, size: 4 */
  inline MyClass2(int initialValue)
  : value{initialValue}
  {
  }
  
  inline void executeLambda()
  {
        
    class __lambda_10_28  /* size: 8, align: 8 */
    {
      public: 
      inline void operator()() const
      {
        __this->value++;
                
        class __lambda_12_32  /* size: 4, align: 4 */
        {
          public: 
          inline /*constexpr */ void operator()()
          {
            (&__this)->value++;
          }
          
          private: 
          MyClass2 __this;                /* offset: 0, size: 4 */
          
          public:
          __lambda_12_32(const MyClass2 & _this)
          : __this{_this}
          {}
          
        };
        
        __lambda_12_32 innerLambda = __lambda_12_32{*this}; // LINE_UNDER_CONSIDERATION
        innerLambda.operator()();
      }
      
      private: 
      MyClass2 * __this;              /* offset: 0, size: 8 */
      
      public:
      __lambda_10_28(MyClass2 * _this)
      : __this{_this}
      {}
      
    };
    
    __lambda_10_28 outerLambda = __lambda_10_28{this};
    static_cast<const __lambda_10_28>(outerLambda).operator()();
  }
  
  // inline constexpr MyClass2(const MyClass2 &) noexcept = default;
};

Please refer to the line marked as "LINE_UNDER_CONSIDERATION".

*this that refers to the class __lambda_10_28 is being passed to the constructor that is taking MyClass2's object as an argument. How is that even correct? From the test case's point of view, it is correct that the constructor takes in the MyClass2 argument because we are capturing that only in the innerLambda.

Seeing such a transformation of this piece of code, I'm curious as to how does GCC know that the this that is getting passed is of the class MyClass2 and not the internally created lambda class?


Solution

  • This is a bug in cppinsight.

    The line should be

    __lambda_12_32 innerLambda = __lambda_12_32{*__this};
    

    __this is the this pointer refering to the instance of MyClass2 passed when constructing the outer lambda.

    Consider that the output cppinsight shows you is not actually exactly what gcc does internally. Neither is it proper C++ code. For example all identifiers used in the output are reserved for the implementation and if you use them in user code then your code invokes undefined behavior. The output of cppinsight is produced by cppinsight, it can give you a glimpse of what the compiler does internally, but it should be taken with a grain of salt. There can be bugs.