考虑这个例子,它将一个变量声明为 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(主干)接受此代码。 (wandbox)
GCC (trunk) 失败并显示以下错误消息 (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 上下文。
最佳答案
两种实现都存在错误,但我倾向于认为 GCC 在这里得到了正确的答案。
放弃对 i
的捕获导致 Clang 拒绝编译代码。这意味着它显然在某个地方存在错误。
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:
- [...]
- 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;
- [...]
Clang 的行为是精神 split 症:如果使用 i
在正文中不是 odr 使用,那么它不需要被捕获,但是如果显式捕获被删除,它会拒绝 OP 中的代码; OTOH,如果是 odr-use,则按上述 unwrap(i)
不是常量表达式,因此它应该拒绝 x
的初始化.
GCC 的 lambda 实现在 odr 使用方面非常糟糕。它会在超早期进行不断折叠,从而导致各种微妙的恶作剧。另一方面,对于显式捕获,它会转换所有用途,无论它是否实际上是 odr 用途。积极的常量折叠意味着如果捕获 i
,它会接受 OP 的代码。被删除。
假设 unwrap(i)
是否使用 ODR i
,那么根据 [expr.const]/2.12,OP 的代码格式错误是正确的。
是否 unwrap(i)
实际使用 odr i
?那个问题boils down是否复制初始化unwrap
的参数对象算作对 i
应用左值到右值的转换.我在标准中没有看到任何明确说明此处应用左值到右值转换的内容,而是 [dcl.init]/17.6.2表示我们调用了一个构造函数(在本例中,是简单的隐式定义的复制构造函数)传递 i
作为绑定(bind)到其参数的参数,引用绑定(bind)是 odr-use 的经典示例。
可以肯定的是,应用从左到右的转换会导致复制初始化 integral_constant<int, 42>
来自 i
的对象,但这里的问题是,标准中没有任何内容相反 - integral_constant<int, 42>
的所有复制初始化来自 i
的对象计为从左到右的转化。
关于c++ - GCC 和 Clang 不同意 C++17 constexpr lambda 捕获,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44386415/