GCC 和 Clang 不同意 C++17 constexpr lambda 捕获
GCC and Clang disagree about C++17 constexpr lambda captures
考虑这个示例,它声明一个变量为 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(主干)失败并显示以下错误消息 (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
of e
, 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 的行为是精神分裂的:如果在正文中使用 i
不是 odr-use,那么它不需要被捕获,但它拒绝 OP 中的代码,如果显式捕获被删除; OTOH,如果它是 odr-use,那么上面的 unwrap(i)
不是常量表达式,因此它应该拒绝 x
.
的初始化
GCC 的 lambda 实现在 odr-use 方面非常糟糕。它会超早地不断折叠,导致各种微妙的恶作剧。另一方面,对于显式捕获,它会转换所有用途,无论它是否实际上是 odr-use。激进的常量折叠意味着如果删除 i
的捕获,它会接受 OP 的代码。
假设 unwrap(i)
确实使用了 odr-use i
,那么根据 [expr.const]/2.12,OP 的代码格式错误是正确的。
unwrap(i)
实际上是 odr-use i
吗?这个问题 boils down to whether copy-initializing the parameter object of unwrap
counts as applying an lvalue-to-rvalue conversion to 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 表明我们调用一个构造函数(在这种情况下,是简单的隐式定义的复制构造函数)传递 i
作为参数绑定到它的参数,引用绑定是 odr-use 的经典示例.
可以肯定的是,应用 l-to-r 转换会导致从 i
复制初始化 integral_constant<int, 42>
对象,但这里的问题是标准中没有说明相反 - 来自 i
的 integral_constant<int, 42>
对象的所有复制初始化都算作 l-to-r 转换。
考虑这个示例,它声明一个变量为 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(主干)失败并显示以下错误消息 (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 的行为是精神分裂的:如果在正文中使用 i
不是 odr-use,那么它不需要被捕获,但它拒绝 OP 中的代码,如果显式捕获被删除; OTOH,如果它是 odr-use,那么上面的 unwrap(i)
不是常量表达式,因此它应该拒绝 x
.
GCC 的 lambda 实现在 odr-use 方面非常糟糕。它会超早地不断折叠,导致各种微妙的恶作剧。另一方面,对于显式捕获,它会转换所有用途,无论它是否实际上是 odr-use。激进的常量折叠意味着如果删除 i
的捕获,它会接受 OP 的代码。
假设 unwrap(i)
确实使用了 odr-use i
,那么根据 [expr.const]/2.12,OP 的代码格式错误是正确的。
unwrap(i)
实际上是 odr-use i
吗?这个问题 boils down to whether copy-initializing the parameter object of unwrap
counts as applying an lvalue-to-rvalue conversion to 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 表明我们调用一个构造函数(在这种情况下,是简单的隐式定义的复制构造函数)传递 i
作为参数绑定到它的参数,引用绑定是 odr-use 的经典示例.
可以肯定的是,应用 l-to-r 转换会导致从 i
复制初始化 integral_constant<int, 42>
对象,但这里的问题是标准中没有说明相反 - 来自 i
的 integral_constant<int, 42>
对象的所有复制初始化都算作 l-to-r 转换。