GCC和Clang不同意C ++ 17 constexpr lambda捕获

时间:2017-06-06 09:35:36

标签: c++ lambda c++17

考虑这个将变量声明为constexpr的示例,通过lambda中的副本捕获它,并声明另一个constexpr变量,该变量是constexpr函数从原始变量中解开非类型模板参数的结果。

#include <utility>

template<int I>
constexpr auto unwrap(std::integral_constant<int, I>) {
  return I;
}

int main() {
  constexpr auto i = std::integral_constant<int, 42>{};
  constexpr auto l = [i]() {
    constexpr int x = unwrap(i);
  };
}

Clang(trunk)接受此代码。 (wandbox

GCC(主干)失败,并显示以下错误消息(wandbox):

lambda_capture.cpp:11:31: error: the value of ‘i’ is not usable in a constant expression
     constexpr int x = unwrap(i);
                               ^
lambda_capture.cpp:10:28: note: ‘i’ was not declared ‘constexpr’
   constexpr auto l = [i]() {

哪个编译器正确?在我看来,这是一个GCC错误,其中lambda捕获的constexpr-ness没有正确传播到lambda上下文。

1 个答案:

答案 0 :(得分:7)

这两个实现都有漏洞,但我倾向于认为GCC在这里得到了正确答案。

删除i的捕获会导致Clang拒绝编译代码。这意味着它显然在某处存在错误。

[expr.const]/2.12

  

除非进行评估,否则表达式e是核心常量表达式   根据抽象机器的规则,e将评估   以下表达式之一:

     
      
  • [...]
  •   
  • lambda-expression 中,对[em] lambda-expression 之外定义的自动存储持续时间的变量的引用,   引用将是odr-use;
  •   
  • [...]
  •   

Clang的行为是精神分裂的:如果在身体中使用i不是使用odr,那么就不需要捕获它,但是如果显式捕获是明确的,它会拒绝OP中的代码删除; OTOH,如果是odr-use,那么上面的unwrap(i)不是常量表达式,因此它应该拒绝x的初始化。

GCC的lambda实现在使用odr方面非常糟糕。它确实超早期折叠,导致各种微妙的恶作剧。另一方面,对于显式捕获,它转换所有用途,无论它是否实际上是一种使用。积极的常量折叠意味着如果删除i的捕获,它会接受OP的代码。

假设unwrap(i)使用odr-use i,那么根据[expr.const] /2.12,OP的代码格式不正确是正确的。

unwrap(i)实际上是否使用了i?关于复制初始化unwrap的参数对象的问题boils down是否计算为将{左值 - 右值转换应用于i。我没有在标准中看到任何明确表示在这里应用左值到右值的转换,而[dcl.init]/17.6.2表示我们调用构造函数(在这种情况下,是一个简单的隐式定义的复制构造函数)传递i作为与其参数绑定的参数,而引用绑定是odr-use的典型示例。

可以肯定的是,应用l-to-r转换会导致integral_constant<int, 42> i对象的复制初始化,但问题是标准中没有任何内容表示反转 - 来自integral_constant<int, 42>的{​​{1}}对象的所有复制初始化都计为l-to-r转换。