也许这里只是程序员,但我试图理解为什么编译器在这里抱怨。
// cexpr_test.cpp
#include <initializer_list>
constexpr int test_cexpr(std::initializer_list<const char*> x)
{
return (int) (*x.begin())[0]; // ensuring the value isn't optimized out.
}
int main()
{
constexpr int r1 = test_cexpr({ "why does this work," });
constexpr std::initializer_list<const char*> broken { "but this doesn't?" };
constexpr int r2 = test_cexpr(broken);
return r1 + r2;
}
使用g++ -std=c++11 -Wall -Werror cexpr_test.cpp
编译时产生的消息如下。令人困惑的是,它构造第一个初始化器列表时没有任何问题。我在这里想念什么?
cexpr_test.cpp: In function ‘int main()’:
cexpr_test.cpp:12:76: error: ‘const std::initializer_list<const char*>{((const char* const*)(&<anonymous>)), 1}’ is not a constant expression
12 | constexpr std::initializer_list<const char*> broken { "but this doesn't?" };
|
答案 0 :(得分:1)
问题在于此处broken
本身的初始化。什么是std::initializer_list
?它有什么用?这是一种引用类型(即以某种方式引用了另一个对象),并且由c样式数组支持。这个c样式数组的属性决定了initializer_list是否可以是constexpr变量。对于这些属性,我们可以咨询 [dcl.init.list] 。
5构造了类型为
std::initializer_list<E>
的对象 从初始化程序列表中,就好像实现已生成并 实现类型为“N
const E
的数组的prvalue,其中N
为 初始化程序列表中的元素数。那个的每个要素 数组将使用的相应元素进行复制初始化 初始化程序列表,而std::initializer_list<E>
对象是 构造以引用该数组。 [注意:构造函数或 为副本选择的转换功能应可在 初始化程序列表的上下文。 —尾注]如果缩小 需要转换才能初始化任何元素,程序 格式不正确。 [示例:struct X { X(std::initializer_list<double> v); }; X x{ 1,2,3 };
初始化的方式大致等同于 这个:
const double __a[3] = {double{1}, double{2}, double{3}}; X x(std::initializer_list<double>(__a, __a+3));
假设实现可以构造一个
initializer_list
有一对指针的对象。 —示例]6数组具有与任何其他临时对象相同的生存期, 除了从数组初始化
initializer_list
对象 延长数组的寿命,就像将引用绑定到 一个临时的。 [示例:typedef std::complex<double> cmplx; std::vector<cmplx> v1 = { 1, 2, 3 }; void f() { std::vector<cmplx> v2{ 1, 2, 3 }; std::initializer_list<int> i3 = { 1, 2, 3 }; } struct A { std::initializer_list<int> i4; A() : i4{ 1, 2, 3 } {} // ill-formed, would create a dangling reference };
对于
v1
和v2
,initializer_list
对象是 函数调用,因此为{ 1, 2, 3 }
创建的数组具有 全表达寿命。对于i3
,initializer_list
对象是 一个变量,因此数组在变量的生存期内一直存在。 对于i4
,initializer_list
对象在 构造函数的ctor-initializer,就像通过将临时数组绑定到 引用成员,因此程序格式不正确([class.base.init])。 — end example] [注意:该实现可自由分配 只读内存中的数组(如果显式数组具有相同的数组) 初始化程序可以这样分配。 —尾注]
因此,此数组就像常量引用所引用的任何其他临时对象一样。这意味着我们实际上可以将您的最小示例减少到更小
constexpr int test_cexpr(int const & x)
{
return x;
}
int main()
{
constexpr int r1 = test_cexpr(0);
constexpr int const &broken = 0;
constexpr int r2 = test_cexpr(broken);
return r1 + r2;
}
这将产生the exact same behavior and error。我们可以直接将0
作为参数传递给constexpr函数,并绑定引用,甚至可以在函数内部引用它。但是,constexpr引用可能未使用0初始化。零不是有效的初始化程序的原因是,它需要实例化临时int
对象。该临时变量不是静态变量,因此不能用于初始化constexpr引用。就这么简单。
相同的理由也适用于您的情况。实现的临时数组不是具有静态存储持续时间的对象,因此它可能不能用于初始化constexpr引用类型。
直接将参数传递给test_cexpr
时起作用的原因是,相应的参数本身不是constexpr变量。这意味着它可以成功绑定。之后,它所绑定的东西必须可以在常量表达式中使用。在此无需赘述:由于在这种情况下,临时项具有完整的表达式生存期(而不是生存期的延长),因此可以在常量表达式中使用。