首页 文章

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

提问于
浏览
19

考虑这个将变量声明为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 回答

  • 7

    两个实现都被窃听,但我倾向于认为GCC在这里得到了正确的答案 .


    删除 i 的捕获会导致Clang拒绝编译代码 . 这意味着它显然在某个地方有一个bug .

    [expr.const]/2.12

    表达式e是核心常量表达式,除非根据抽象机器的规则评估e将评估以下表达式之一:[...]在lambda表达式中,对[...]的引用一个变量,其自动存储持续时间在lambda-expression之外定义,其中引用将是odr-use; [...]

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


    GCC 's lambda implementation is woefully bad with respect to odr-use. It does constant-folding ultra-early, resulting in all kinds of subtle mischief. On the other hand, for explicit captures it transforms all uses, whether or not it'实际上是一种使用方法 . 积极的常量折叠意味着如果删除 i 的捕获,它接受OP的代码 .

    假设 unwrap(i) 确实使用了 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>integral_constant<int, 42> 对象的复制初始化,但这里的问题是标准中没有任何内容反过来说 - integral_constant<int, 42> 对象的所有复制初始化 i 算作l-to-r转换 .

相关问题