lambda中捕获变量的decltype():GCC bug和/或Clang bug?

时间:2015-06-25 03:52:49

标签: c++ c++11 lambda c++14 decltype

我已检查过GCC buglistClang buglist,但看不到任何相关内容。

This Wandbox link显示了一些C ++ 11 / C ++ 14代码,用于对lambdas捕获的各种decltype(x)行使decltype((x))x。 GCC和Clang为此代码提供了不同的答案。其中哪一个,如果有的话,是正确的?

这是令人讨厌的片段:

// inside main()
int i = 42;
int &j = i;
[j=j](){
    static_assert(std::is_same<decltype(j), GCC(const) int>::value,""); // A
    static_assert(std::is_same<decltype((j)), const int&>::value,""); // B
}();
[=](){
    static_assert(std::is_same<decltype(j), int&>::value,""); // C
    static_assert(std::is_same<decltype((j)), CLANG(const) int&>::value,""); // D
}();

其中:

#ifdef __clang__
 #define CLANG(x) x
 #define GCC(x)
#else
 #define GCC(x) x
 #define CLANG(x)
#endif

我相信在这两种情况下,实际捕获的东西(*)是一个(非常量)int初始化为j的副本值(也就是说,i的值)。由于lambda未标记为mutable,因此其operator()将成为const成员函数。有了这些先决条件,让我们继续......

在线// A,GCC告诉我,显式初始捕获的j的decltype是const int,当我几乎肯定它应该是int时(每个Clang)。

// B行上,两个编译器都同意(j)是一个引用const int的左值(因为lambda没有标记为mutable);这对我来说非常有意义。

// C行上,两个编译器都同意j是一个引用第2行声明的int&的名称。这是5.1.2 [expr.prim.lambda]的结果] / 19,或者更确切地说,当事件发生的时候 - 那个 - 子句 - 不是 - 被调用的结果。在[=] lambda中,名称 j引用外部作用域中的j,但表达式 (j) }指的是在(j)被捕获时存在的j。我不完全了解这是如何工作的或为什么它是可取的,但它确实存在。我愿意规定这不是任何编译器中的错误。

// D行,Clang告诉我(j)是一个引用const int的左值,而GCC告诉我它是一个引用非const 的左值INT。我很确定Clang是对的,GCC是错的; <{1}}应该是相同的,无论是隐式还是显式捕获decltype((j))

所以:

  • 我的解释是否正确(根据标准)?
  • C ++ 11和C ++ 14(和C ++ 1z)之间的正确答案是否有变化?
  • GCC中的j// A都是错误吗?
  • 这些错误已经提交了吗?

(*) - 事实上,第二个lambda在技术上捕获没有任何内容,因为它在任何评估的上下文中都不使用// D。这就是为什么行j// A给出了不同的答案。但我不知道任何好的术语 - -being-done-to - // C,所以我只是说“捕获”。

1 个答案:

答案 0 :(得分:6)

我认为两个编译器对于(A)是错误的,而gcc对于(D)是错误的。
我认为gcc对于(A)和(D)是错误的,而clang对于两者都是正确的。

[expr.lambda.prim]的相关部分是:

  

init-capture 的行为就像它声明并显式捕获“auto init-capture ;”形式的变量一样   其声明区域是 lambda-expression 复合语句,但以下情况除外:
   - 如果捕获是通过复制(见下文),则为捕获声明的非静态数据成员和   变量被视为引用同一对象的两种不同方式,它具有生命周期   非静态数据成员,不执行其他复制和销毁,

  

lambda-expression 复合语句中的每个 id-expression ,这是一个odr-use (3.2)的   通过副本捕获的实体转换为对相应的未命名数据成员的访问权限   关闭类型。

decltype(j)不是j的使用,因此不应考虑此类转换。因此,在[=]{...}的情况下,decltype(j)应该产生int&。但是,在 init-capture 的情况下,行为就好像存在auto j = j;形式的变量,变量j指的是同一个未命名的非变量 - 静态数据成员,不需要这样的转换。因此,对于[j=j]{...}decltype(j)应该生成该变量的类型 - int。绝对不是const int。这是一个错误。

下一个相关部分:

  

decltype((x))每次出现x是一个可能带括号的 id-expression ,它将自动存储持续时间的实体命名为x被转换访问闭包类型的相应数据成员,如果x是对所表示实体的使用,则会声明该成员。    [例如:

void f3() {
    float x, &r = x;
    [=] {                       // x and r are not captured (appearance in a decltype operand is not an odr-use)
        decltype(x) y1;         // y1 has type float
        decltype((x)) y2 = y1;  // y2 has type float const& because this lambda
                                // is not mutable and x is an lvalue
        decltype(r) r1 = y1;    // r1 has type float& (transformation not considered)
        decltype((r)) r2 = y2;  // r2 has type float const&
    }
}
     

-end example]

该示例进一步说明隐式副本案例中decltype(j)应为int&,并且还表明decltype((j))被视为x是相应的数据成员已声明:在两种情况下都是int const&(因为lambda不是mutablej是左值)。您的(C)和(D)个案完全反映了示例中的r1r2声明。虽然这些例子不是规范性的,但肯定表明gcc因为有不同的行为而错了。