这是this topic的某种后续行动,并涉及其中的一小部分。与前一主题一样,我们假设我们的编译器具有constexpr
和std::initializer_list
的{{1}}函数。现在,让我们直截了当。
std::array
#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
和常量表达式的论文,但这种行为对我来说仍然没有任何意义。为什么第一个例子被认为是有效的常量表达而不是第二个?我欢迎任何解释,以便我可以在此之后安息。
注意:我会马上准确的,Clang将无法编译第一个代码段,因为它没有实现为C ++计划的constexpr
库添加项14 。我使用了GCC 4.7。
编辑:好的,这里有一个很好的例子来展示被拒绝的内容和不被拒绝的内容:
constexpr
答案 0 :(得分:2)
我弄清楚这里发生了什么:
constexpr std::initializer_list<int> b = { a[0], a[1], a[2] };
类型为a[0]
的 const int&
隐式转换为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’
同样的行为是在clang。
我认为这种转换是合法的,没有任何相反的说法。
关于const int&
到const int
次转化,[expr]第5段:
如果表达式最初具有“对T的引用”类型,则类型为 在进行任何进一步分析之前调整到T.表达式指定 由引用表示的对象或函数,表达式为 lvalue或xvalue,取决于表达式。
在这种情况下,a[0]
表达式的结果是const int
类型的临时x值。
关于constexpr初始化程序中的隐式转换,[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
- 结束示例]
答案 1 :(得分:2)
您的示例都是格式不正确。
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 ++&#39; s <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)
构造函数。我不确定为什么GCC仍然拒绝这一点。