[英]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 ofe
, 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>
对象,但问题是标准中没有任何内容表示相反 - 所有复制初始化来自i
的integral_constant<int, 42>
对象计为l-to-r转换。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.