考虑这个将变量声明为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 回答
两个实现都被窃听,但我倾向于认为GCC在这里得到了正确的答案 .
删除
i
的捕获会导致Clang拒绝编译代码 . 这意味着它显然在某个地方有一个bug .[expr.const]/2.12:
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转换 .