对常量表达式的困惑
Confusion about constant expressions
这是这个主题的某种后续,并涉及其中的一小部分。与前面的主题一样,让我们考虑我们的编译器具有用于std::initializer_list
和std::array
的constexpr
函数。现在,让我们直奔主题。
这有效:
#include <array>
#include <initializer_list>
int main()
{
constexpr std::array<int, 3> a = {{ 1, 2, 3 }};
constexpr int a0 = a[0];
constexpr int a1 = a[1];
constexpr int a2 = a[2];
constexpr std::initializer_list<int> b = { a0, a1, a2 };
return 0;
}
这不会:
#include <array>
#include <initializer_list>
int main()
{
constexpr std::array<int, 3> a = {{ 1, 2, 3 }};
constexpr std::initializer_list<int> b = { a[0], a[1], a[2] };
return 0;
}
它崩溃并显示此错误:
error: 'const std::initializer_list<int>{((const int*)(&<anonymous>)), 3u}' is not a constant expression
尽管我同时阅读了一些关于constexpr
和恒定表达式的论文,但这种行为对我来说仍然没有任何意义。为什么第一个示例被认为是有效的常量表达式而不是第二个示例?我欢迎任何解释,以便我以后可以安息。
注意:我会立即精确地使用它,Clang 将无法编译第一个代码片段,因为它没有实现计划在 C++14 中添加的constexpr
库。我使用了GCC 4.7。
编辑:好的,这里有一个大的例子来显示什么被拒绝,什么没有:
#include <array>
#include <initializer_list>
constexpr int foo = 42;
constexpr int bar() { return foo; }
struct eggs { int a, b; };
int main()
{
constexpr std::array<int, 3> a = {{ 1, 2, 3 }};
constexpr int a0 = a[0];
constexpr int a1 = a[1];
constexpr int a2 = a[2];
// From Xeo and Andy tests
constexpr std::array<int, 1> a = { bar() }; // OK
constexpr std::array<int, 3> b = {{ a[0], a[1], a[2] }}; // OK
std::initializer_list<int> b = { a[0], a[1], a[2] }; // OK
constexpr std::initializer_list<int> b = { a0, a1, a2 }; // OK
constexpr std::initializer_list<int> b = { foo }; // OK
constexpr std::initializer_list<int> c = { bar() }; // ERROR
constexpr std::initializer_list<int> b = { a[0], a[1], a[2] }; // ERROR
// From Matheus Izvekov and Daniel Krügler
constexpr eggs good = { 1, 2 }; // OK
constexpr std::initializer_list<eggs> bad = { { 1, 2 }, { 3, 4 } }; // ERROR
constexpr std::initializer_list<eggs> bad2 = { good, good }; // ERROR
return 0;
}
出了这里发生了什么:
constexpr std::initializer_list<int> b = { a[0], a[1], a[2] };
类型 const int&
的a[0]
隐式转换为 const int
类型的临时。然后 g++ 将其转换为 const int*
以传递到initializer_list
私有构造函数中。在最后一步中,它采用临时地址,因此它不是一个常量表达式。
问题在于隐式转换为 const int。 示例:
constexpr int v = 1;
const int& r = v; // ok
constexpr int& r1 = v; // error: invalid initialization of reference of
// type ‘int&’ from expression of type ‘const int’
同样的行为也在叮当声中。
我认为这种转换是合法的,没有什么是相反的。
关于const int&
至const int
转换,[expr]第5款:
如果表达式最初具有"引用 T"的类型,则类型为 在进行任何进一步分析之前调整为 T。表达式指定 引用表示的对象或函数,表达式为 左值或 x值,具体取决于表达式。
在这种情况下,a[0]
表达式的结果是类型 const int
的临时 xvalue。
初始值设定项中的隐式转换,[dcl.constexpr] 第 9 段:
。转换初始值设定项时使用的每个隐式转换 表达式和用于初始化的每个构造函数调用 应为常量表达式中允许的表达式之一。
关于临时地址,[expr.const] 第 2 段:
。对 constexpr 函数的调用,其参数在 替换为函数调用替换,不要 产生常量表达式;[ 示例:
constexpr const int* addr(const int& ir) { return &ir; } // OK static const int x = 5; constexpr const int* xp = addr(x); // OK: (const int*)&(const int&)x is an // address contant expression constexpr const int* tp = addr(5); // error, initializer for constexpr variable // not a constant expression; // (const int*)&(const int&)5 is not a // constant expression because it takes // the address of a temporary
— 结束示例 ]
你的例子都是格式错误的。
tl/dr:初始值设定项是非常量值的,因为它在每次计算函数时都引用不同的临时值。
宣言:
constexpr std::initializer_list<int> b = { a0, a1, a2 };
创建一个类型为 const int [3]
(C++11 [dcl.init.list]p5( 的临时数组,然后将std::initializer_list<int>
对象绑定到该临时对象,就像绑定对它的引用一样(C++11 [dcl.init.list]p6(。
现在,到 C++11 [expr.const]p4,
对于数组或类类型的文字常量表达式,每个子对象 [...] 应由常量表达式初始化。[...]地址常量表达式 [...] 计算为具有静态存储持续时间的对象的地址。
由于b
具有自动存储持续时间,因此当std::initializer_list<int>
对象绑定到临时const int [3]
时,临时对象也会被赋予自动存储持续时间,因此b
的初始化不是常量表达式,因为它指的是没有静态存储持续时间的对象地址。因此,b
的声明格式不正确。
为什么 GCC 接受一些constexpr
std::initializer_list
对象
在初始值设定项适当微不足道的情况下,GCC(和 Clang(将数组提升到全局存储,而不是每次都创建新的临时存储。但是,在GCC中,这种实现技术会泄漏到语言语义中 - GCC将数组视为具有静态存储持续时间,并接受初始化(作为C++11规则的意外或故意扩展(。
解决方法(仅限 Clang(
您可以通过为std::initializer_list<int>
对象提供静态存储持续时间来使示例有效:
static constexpr std::initializer_list<int> b = { a0, a1, a2 };
这反过来又为数组提供了临时的静态存储持续时间,这使得初始化成为常量表达式。
使用 Clang 和 libc++(constexpr
添加到 libc++ 的 <array>
中,并在适当的位置<initializer_list>
(,这种添加static
的调整足以让你的例子被接受。
使用 GCC,示例仍然被拒绝,诊断如下:
<stdin>:21:61: error: ‘const std::initializer_list<int>{((const int*)(& _ZGRZ4mainE1c0)), 1u}’ is not a constant expression
在这里,_ZGRZ4mainE1c0
是临时的生命周期扩展数组(具有静态存储持续时间(的损坏名称,我们可以看到 GCC 隐式调用(私有(initializer_list<int>(const int*, size_t)
构造函数。我不确定为什么海湾合作委员会仍然拒绝这一点。
- 不能在初始值设定项列表中将非常量表达式从类型 'int' 缩小到'unsigned long long'
- 当一个值是非常量但用常量表达式初始化时使用constexpr
- gcc和clang在表达式是否为常量求值的问题上存在分歧
- 使用自动推导的 lambda 参数作为常量表达式
- 生成提升::hana::set 的常量表达式问题
- 为什么不能用常量表达式声明数组?
- 不是 lambda 函数中的常量表达式
- 函数调用在常量表达式中必须具有常量值
- 错误:constexpr 变量'struct2Var'必须由常量表达式初始化
- 如何在常量计算表达式中获取编译时错误?
- 关于在需要常量表达式的上下文中使用的glvalue常量表达式的问题
- 生成 constexpr 字符串表,不能产生常量表达式
- 整体模板参数。错误:在常量表达式中使用'this'
- 如何在满足常量表达式的同时将整数传递给指针,传递给 std::array<double、integer>?
- 编译器错误:函数调用在常量表达式中必须有一个常量值
- 错误:'new'不能出现在常量表达式中
- 我可以写出小于 -0.5 两个 ulps 的常量表达式双精度吗?
- 编译器在传递 const 变量时返回错误:模板参数不是常量表达式
- 为什么我不能在非常量表达式上使用此模板阶乘函数?
- C++ 使用变量而不是常量表达式初始化数组