Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GCC can't capture 'this' pointer to templated type using init-capture

A templated class can capture its own this pointer in a lambda:

template <typename T>
class Foo {
  public:
    void foo(void) {}
    auto getCallableFoo(void) {
      return [this]() { this->foo(); };
    }
};

This and all other Foo examples can be tested using the following code:

int main()
{
  Foo<int> f;
  auto callable = f.getCallableFoo();
  callable();
}

However, if instead an init-capture is used, this no longer works with GCC:

    auto getCallableFoo(void) {
      return [ptr = this]() { ptr->foo(); };
    }

Error message (from GCC 5.1):

error: ‘Foo<T>::getCallableFoo()::<lambda()>::__ptr’ has incomplete type

Clang 3.7 appears to compile and run this code without error. (I'm actually using a version compiled from source from before 3.7 was released, but I don't expect this has broken since then.)

Init-capture is supposed to behave like assignment to auto, but the following code appears to work without error in GCC:

// New method in Foo:
auto getPtr(void) {
  return this;
}

// Usage:
auto ptr = f.getPtr();
ptr->foo();

So why isn't the ptr value able to capture this in GCC? Is this a bug?

One other consideration is that, according to CppReference, this is treated as a separate syntactical case from every other capture-list type. So that may be one hint toward why GCC treats these cases differently. But it is not clear to me what (if any) special handling is done for this special case, or why it's a special case at all.

EDIT: It appears that this does work:

return [ptr = static_cast<decltype(this)>(this)]() { ptr->foo(); };

This makes no sense to me, because decltype (unlike auto) infers exactly the type of its argument, so the static_cast shouldn't actually be affecting anything.

EDITS 2,3,4: Here's a complete list of expressions that I've tried with both compilers, with comments indicating which compiler(s) accept each expression:

[this]() { this->foo(); };        // Both: work
[ptr = this]() { ptr->foo(); };   // GCC fails
[ptr = static_cast<decltype(this)>(this)]() { ptr->foo(); };   // Both: works (!!!)
[ptr(this)]() { ptr->foo(); };   // GCC fails
[ptr{this}]() { ptr->foo(); };   // GCC works (!!!!!!!!), Clang doesn't work (infers initializer list)
[ptr = {this}]() { ptr->foo(); };   // Both: fail (infers initializer list)
[ptr = &*this]() { ptr->foo(); };  // Both: work
[ptr = &*(this)]() { ptr->foo(); };  // Both: work

For [ptr{this}], my version of Clang (a pre-release 3.7) warns that the interpretation will change; currently it infers an initializer list, but presumably later versions will (or already do) infer the type of this in accordance with the new auto rules from N3922.

It shocks me that GCC permits [ptr{this}] but not [ptr(this)]. I have no explanation for this.

like image 509
Kyle Strand Avatar asked Jan 20 '16 00:01

Kyle Strand


1 Answers

It's a bug.

This is a bug. I've submitted a GCC bug report for this problem. It has now been fixed in GCC's trunk.

Workaround

As noted by Revolver_Ocelot, &* appears to force g++ to perform the correct type-deduction. My current workaround (which is inside a macro taking some pointer expression that might be this) is therefore to capture [ptr = &*(ptr_expr)].

Why did this happen?

As noted above, GCC's Jason Merrill has fixed this in GCC's trunk. He comments that the this pointer requires special handling in lambda captures; specifically, it is treated as though it were not a dependent type. Previously, this special handling applied to [this] but not to [ptr = this].

like image 82
Kyle Strand Avatar answered Oct 25 '22 01:10

Kyle Strand