考虑这个将变量声明为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);
};
}
Run Code Online (Sandbox Code Playgroud)
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]() {
Run Code Online (Sandbox Code Playgroud)
哪个编译器正确?在我看来,这是一个GCC错误,其中lambda捕获的constexpr-ness没有正确传播到lambda上下文.
两个实现都被窃听,但我倾向于认为GCC在这里得到了正确的答案.
删除捕获i原因Clang拒绝编译代码.这意味着它显然在某个地方有一个bug.
表达式
e是核心常量表达式,除非按照e抽象机器的规则评估以下表达式之一:
- [...]
- 在lambda表达式中,对一个变量的引用,该变量具有在lambda表达式之外定义的自动存储持续时间,其中引用将是odr-use;
- [...]
Clang的行为是精神分裂的:如果i身体中的使用不是一种使用,那么它就不需要被捕获,但如果删除了明确的捕获,它就会拒绝OP中的代码; OTOH,如果是odr-use,那么通过上面unwrap(i)的不是常量表达式,所以它应该拒绝初始化x.
GCC的lambda实现在使用odr方面非常糟糕.它确实超早期折叠,导致各种微妙的恶作剧.另一方面,对于显式捕获,它转换所有用途,无论它是否实际上是一种使用.积极的常量折叠意味着如果i删除捕获,它接受OP的代码.
假设unwrap(i)使用odr i,那么根据[expr.const] /2.12,OP的代码是不正确的,这是正确的.
是否unwrap(i)真正ODR-使用i?这个问题归结为复制初始化count的参数对象是否unwrap应用左值到右值的转换i.我没有在标准中看到任何明确表示在这里应用左值到右值的转换,而是[dcl.init] /17.6.2表示我们调用了一个构造函数(在这种情况下,隐式定义了一个简单的复制构造函数)i作为参数绑定到其参数传递,引用绑定是odr-use的典型示例.
可以肯定的是,应用l-to-r转换会导致integral_constant<int, 42>对象的复制初始化i,但问题是标准中没有任何内容表示相反 - integral_constant<int, 42>对象的所有复制初始化从icount计为l到转换.
| 归档时间: |
|
| 查看次数: |
1153 次 |
| 最近记录: |