提问者:小点点

GCC和Clang在C17 constexpr lambda捕获问题上存在分歧


考虑这个例子,它将一个变量声明为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)接受此代码。(魔杖盒)

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捕获的constexprness没有正确地传播到lambda上下文。


共1个答案

匿名用户

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

放弃捕获 i 会导致 Clang 拒绝编译代码。这意味着它显然在某处有一个错误。

[expr. const]/2.12:

表达式e是核心常量表达式,除非根据抽象机的规则对e的求值将求值以下表达式之一:

  • […]
  • 在lambda表达式中,对[…]变量的引用,该变量具有在该lambda表达式外部定义的自动存储持续时间,其中该引用将是odr使用
  • […]

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

GCC的lambda实现在odr使用方面非常糟糕。它会在极短的时间内不断折叠,导致各种微妙的恶作剧。另一方面,对于显式捕获,它转换所有使用,无论它是否实际是odr使用。激进的常量折叠意味着,如果移除了对i的捕获,它将接受OP的代码。

假设uncel(i)确实odr-usei,那么根据[expr. const]/2.12,OP的代码格式错误是正确的。

< code>unwrap(i)实际上odr-use i吗?这个问题归结为复制初始化< code>unwrap的参数对象是否算作将左值到右值的转换应用于< code>i。我在标准中没有看到任何明确表示在这里应用左值到右值转换的内容,而是[dcl.init]/17.6.2表示我们调用一个构造函数(在本例中,是普通的隐式定义的复制构造函数),将< code>i作为参数绑定到它的参数,引用绑定是odr使用的一个经典示例。

可以肯定的是,应用 l 到 r 转换将导致integral_constant的复制初始化