简体   繁体   English

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

[英]GCC and Clang disagree about C++17 constexpr lambda captures

Consider this example which declares a variable as constexpr, captures it by copy in a lambda, and declares another constexpr variable which is the result of a constexpr function unwrapping a non-type template parameter from the original variable. 考虑这个将变量声明为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) accepts this code. Clang(trunk)接受此代码。 ( wandbox ) wandbox

GCC (trunk) fails with the following error message ( 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]() {

Which compiler is correct? 哪个编译器正确? It seems to me that this is a GCC bug, where the constexpr-ness of lambda captures is not correctly propagated to the lambda context. 在我看来,这是一个GCC错误,其中lambda捕获的constexpr-ness没有正确传播到lambda上下文。

Both implementations are bugged, but I'm inclined to think that GCC got the right answer here. 两个实现都被窃听,但我倾向于认为GCC在这里得到了正确的答案。


Dropping the capture of i causes Clang to refuse to compile the code. 删除i的捕获导致Clang拒绝编译代码。 That means it clearly has a bug somewhere. 这意味着它显然在某个地方有一个bug。

[expr.const]/2.12 : [expr.const] /2.12

An expression e is a core constant expression unless the evaluation of e , following the rules of the abstract machine, would evaluate one of the following expressions: 表达式e是核心常量表达式,除非根据抽象机器的规则评估e将评估以下表达式之一:

  • [...] [...]
  • in a lambda-expression , a reference to [...] a variable with automatic storage duration defined outside that lambda-expression , where the reference would be an odr-use; lambda表达式中 ,对一个变量的引用,该变量具有在lambda表达式之外定义的自动存储持续时间,其中引用将是odr-use;
  • [...] [...]

Clang's behavior is schizophrenic: if the use of i in the body is not an odr-use, then it doesn't need to be captured, yet it rejects the code in the OP if the explicit capture is removed; Clang的行为是精神分裂的:如果在身体中使用i不是一种使用,那么就不需要捕获它,但如果删除了显式捕获,它会拒绝OP中的代码; OTOH, if it is an odr-use, then by the above unwrap(i) isn't a constant expression, and so it should reject the initialization of x . OTOH,如果是odr-use,那么通过上面的unwrap(i)不是常量表达式,因此它应该拒绝x的初始化。


GCC's lambda implementation is woefully bad with respect to odr-use. GCC的lambda实现在使用odr方面非常糟糕。 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's actually an odr-use. 另一方面,对于显式捕获,它转换所有用途,无论它是否实际上是一种使用。 The aggressive constant folding means that it accepts OP's code if the capture of i is removed. 积极的常量折叠意味着如果删除i的捕获,它接受OP的代码。

Assuming that unwrap(i) does odr-use i , then it is correct that, per [expr.const]/2.12, OP's code is ill-formed. 假设unwrap(i)不ODR使用i ,那么它是正确的,每[expr.const] /2.12,OP的代码是形成不良的。


Does unwrap(i) actually odr-use i ? unwrap(i)其实ODR利用i That question boils down to whether copy-initializing the parameter object of unwrap counts as applying an lvalue-to-rvalue conversion to i . 这个问题归结为复制初始化unwrap的参数对象是否计算为对i应用左值到右值的转换。 I don't see anything in the standard that explicitly says that an lvalue-to-rvalue conversion is applied here, and instead [dcl.init]/17.6.2 indicates that we call a constructor (in this case, the trivial implicitly defined copy constructor) passing i as the argument bound to its parameter, and reference binding is a classic example of odr-use. 我没有在标准中看到任何明确表示在这里应用左值到右值的转换,而[dcl.init] /17.6.2表示我们调用了一个构造函数(在这种情况下,隐含地定义了一个简单的复制构造函数)将i作为参数绑定到其参数,并且引用绑定是odr-use的典型示例。

To be sure, applying an l-to-r conversion would result in a copy-initialization of an integral_constant<int, 42> object from i , but the problem here is that nothing in the standard says the converse - that all copy-initializations of an integral_constant<int, 42> object from i count as l-to-r conversions. 可以肯定的是,应用l-to-r转换会导致从i复制初始化一个integral_constant<int, 42>对象,但问题是标准中没有任何内容表示相反 - 所有复制初始化来自iintegral_constant<int, 42>对象计为l-to-r转换。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM