
[英]Validation of an std::initializer_list in constexpr context
[英]Weird behaviour constexpr with std::initializer_list
我试图理解为什么编译器在这里抱怨:
// 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:在函数“int main()”中:cexpr_test.cpp:12:76:错误:“const std::initializer_list{((const char* const*)(&)), 1}”不是常量表达式 12 | constexpr std::initializer_list 损坏 {“但这不是?” }; |
令人困惑的是,为什么它在没有任何问题的情况下构建第一个初始化列表。 我在这里缺少什么?
问题在于这里的broken
本身的初始化。 什么是std::initializer_list
以及它包含什么? 它是一个引用类型(即以某种方式引用另一个对象),并且它由一个 c 样式数组支持。 这个 c 样式数组的属性决定了 initializer_list 是否可以是 constexpr 变量。 我们可以查阅[dcl.init.list]以获得这些属性。
5
std::initializer_list<E>
类型的对象是从初始化列表构造的,就好像实现生成并物化了一个“N
const E
数组”类型的纯右值,其中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])。 — 结束示例 ] [ 注意:如果可以分配具有相同初始值设定项的显式数组,则实现可以在只读内存中自由分配数组。 — 尾注 ]
所以这个数组就像任何其他由常量引用引用的临时对象一样。 这意味着我们实际上可以将您的最小示例减少到更小
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;
}
这会产生完全相同的行为和错误。 我们可以直接将0
作为参数传递给 constexpr 函数,引用绑定,我们甚至可以在函数内部引用它。 然而,constexpr 引用不能用 0 初始化。零不是一个有效的初始化器的原因是它需要物化一个临时的int
对象。 此临时变量不是静态变量,因此不能用于初始化 constexpr 引用。 就那么简单。
同样的推理适用于您的情况。 物化的临时数组不是具有静态存储持续时间的对象,因此它不能用于初始化 constexpr 引用类型。
它在直接将参数传递给test_cexpr
时起作用的原因是相应的参数本身不是 constexpr 变量。 这意味着它可以成功绑定。 之后,它所绑定的东西必须在常量表达式中可用。 无需对此进行过多详细说明:由于在这种情况下临时变量具有完整的表达式生命周期(而不是生命周期延长),因此它可用于常量表达式。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.