【问题标题】:GCC can't capture 'this' pointer to templated type using init-captureGCC 无法使用 init-capture 捕获指向模板类型的“this”指针
【发布时间】:2016-04-25 15:58:09
【问题描述】:

模板类可以在 lambda 中捕获自己的 this 指针:

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

这个和所有其他Foo 示例可以使用以下代码进行测试:

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

但是,如果改为使用 init-capture,则这不再适用于 GCC:

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

错误消息(来自 GCC 5.1):

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

Clang 3.7 似乎编译和运行此代码没有错误。 (我实际上使用的是从 3.7 发布之前的源代码编译的版本,但我不希望从那时起它就坏了。)

初始化捕获的行为应该类似于分配给auto,但以下代码在 GCC 中似乎可以正常工作:

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

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

那么为什么 ptr 值不能在 GCC 中捕获 this 呢?这是一个错误吗?

另一个考虑因素是,according to CppReferencethis 被视为与所有其他捕获列表类型分开的 句法 案例。因此,这可能是 GCC 以不同方式对待这些案例的一个暗示。但我不清楚对这种特殊情况做了什么(如果有的话)特殊处理,或者为什么它是一个特殊情况。

编辑:看来这确实工作:

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

这对我来说毫无意义,因为decltype(与auto 不同)推断准确其参数的类型,因此static_cast 实际上不应该影响任何东西。 p>

EDITS 2,3,4:这是我在两种编译器中都尝试过的表达式的完整列表,cmets 指示哪些编译器接受每个表达式:

[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

对于[ptr{this}],我的 Clang 版本(预发布 3.7)警告解释会改变;目前它推断出一个初始化列表,但可能以后的版本将(或已经这样做)推断thisin accordance with the new auto rules from N3922的类型。

令我震惊的是 GCC 允许 [ptr{this}] 但不允许 [ptr(this)]。我对此没有任何解释。

【问题讨论】:

  • 有趣。请注意,在 GCC 4.9.3 下将其更改为 [ptr = static_cast&lt;const Foo&lt;T&gt;* const&gt;(this)] 对我有效(一旦将 foogetCallableFoo 声明为 const,它们应该是这样)。
  • @Yuushi 嗯。我希望它可以在宏BIND_MEMBER_TO_THIS 中工作,它只是将任意成员函数绑定到对象的this 指针。我想使用更通用的BIND_MEMBER_TO_OBJ_PTR 宏来实现宏,它会初始化捕获指针,但现在我正在解决(明显的)错误,只需使用[this] 而不是重写整个 lambda利用另一个宏。至于foogetCallableFoo,我在这里尽可能地通用——foo() 可以是const 或非const
  • 请注意,宏不能包含强制转换,除非使用decltype。啊啊啊……看来static_cast&lt;decltype(this)&gt;(this),虽然看起来很傻,但确实有效。我将把它添加到问题中。
  • 如果您使用简单的大括号初始化器而不是=,是否会出现相同的错误? [ptr(this)]() { ... }
  • 有趣的是,[ptr = &amp;*this] 工作...

标签: c++ templates inheritance lambda c++14


【解决方案1】:

这是一个错误。

这是一个错误。我已经为这个问题提交了a GCC bug report。现在是fixed in GCC's trunk

解决方法

正如 Revolver_Ocelot 所指出的,&amp;* 似乎强制 g++ 执行正确的类型推断。因此,我当前的解决方法(在宏内部采用一些可能是this 的指针表达式)是捕获[ptr = &amp;*(ptr_expr)]

为什么会这样?

如上所述,GCC 的 Jason Merrill 已在 GCC 的主干中修复了此问题。他认为this 指针需要在 lambda 捕获中进行特殊处理;具体来说,它被视为不是依赖类型。以前,这种特殊处理适用于[this],但不适用于[ptr = this]

【讨论】:

  • 如果您使用任意指针,我会使用一元 + 而不是 &amp;*
  • @T.C.有什么特别的原因吗?我真的不明白一元 + 在应用于指针时通常会有什么含义,但 &amp;* 很明显的意思是“取消引用然后再次获取地址”。
  • @T.C.啊。这是完全有道理的,但如果它是一个空指针,我还是有 UB,因为 lambda 使用 ptr-&gt; 调用函数。
猜你喜欢
  • 1970-01-01
  • 2015-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多