考虑这个将变量声明为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上下文。
答案 0 :(得分:7)
这两个实现都有漏洞,但我倾向于认为GCC在这里得到了正确答案。
删除i
的捕获会导致Clang拒绝编译代码。这意味着它显然在某处存在错误。
除非进行评估,否则表达式
e
是核心常量表达式 根据抽象机器的规则,e
将评估 以下表达式之一:
- [...]
- 在 lambda-expression 中,对[em] lambda-expression 之外定义的自动存储持续时间的变量的引用, 引用将是odr-use;
- [...]
Clang的行为是精神分裂的:如果在身体中使用i
不是使用odr,那么就不需要捕获它,但是如果显式捕获是明确的,它会拒绝OP中的代码删除; OTOH,如果是odr-use,那么上面的unwrap(i)
不是常量表达式,因此它应该拒绝x
的初始化。
GCC的lambda实现在使用odr方面非常糟糕。它确实超早期折叠,导致各种微妙的恶作剧。另一方面,对于显式捕获,它转换所有用途,无论它是否实际上是一种使用。积极的常量折叠意味着如果删除i
的捕获,它会接受OP的代码。
假设unwrap(i)
使用odr-use 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>
i
对象的复制初始化,但问题是标准中没有任何内容表示反转 - 来自integral_constant<int, 42>
的{{1}}对象的所有复制初始化都计为l-to-r转换。