std::initializer_list 的奇怪行为 constexpr
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: In function ‘int main()’:
cexpr_test.cpp:12:76: error: ‘const std::initializer_list{((const char* const*)(&)), 1}’ is not a constant expression
12 | constexpr std::initializer_list broken { "but this doesn't?" };
|
令人困惑的是,为什么它可以毫无问题地构建第一个初始化列表。我在这里错过了什么?
这里的问题出在 broken
本身的初始化上。 std::initializer_list
是什么,它代表什么?它是一种引用类型(即以某种方式引用另一个对象),并且由 c 风格的数组支持。这个 c 风格数组的属性决定了 initializer_list 是否可以是一个 constexpr 变量。我们可以参考 [dcl.init.list] 了解这些属性。
5 An object of type std::initializer_list<E>
is constructed
from an initializer list as if the implementation generated and
materialized a prvalue of type “array of N
const E
”, where N
is
the number of elements in the initializer list. Each element of that
array is copy-initialized with the corresponding element of the
initializer list, and the std::initializer_list<E>
object is
constructed to refer to that array. [ Note: A constructor or
conversion function selected for the copy shall be accessible in the
context of the initializer list. — end note ] If a narrowing
conversion is required to initialize any of the elements, the program
is ill-formed. [ Example:
struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };
The initialization will be implemented in a way roughly equivalent to
this:
const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));
assuming that the implementation can construct an initializer_list
object with a pair of pointers. — end example ]
6 The array has the same lifetime as any other temporary object,
except that initializing an initializer_list
object from the array
extends the lifetime of the array exactly like binding a reference to
a temporary. [ Example:
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
};
For v1
and v2
, the initializer_list
object is a parameter in a
function call, so the array created for { 1, 2, 3 }
has
full-expression lifetime. For i3
, the initializer_list
object is
a variable, so the array persists for the lifetime of the variable.
For i4
, the initializer_list
object is initialized in the
constructor's ctor-initializer as if by binding a temporary array to a
reference member, so the program is ill-formed ([class.base.init]).
— end example ] [ Note: The implementation is free to allocate the
array in read-only memory if an explicit array with the same
initializer could be so allocated. — end note ]
所以这个数组就像任何其他由常量引用引用的临时对象一样。这意味着我们实际上可以将您的最小示例缩减为更小的东西
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 变量。这意味着它可以成功绑定。之后,它所绑定的东西必须在常量表达式中可用。无需对此过多赘述:由于在这种情况下临时变量具有完整的表达式生命周期(而不是延长的生命周期),因此它可用于常量表达式。
我试图理解为什么编译器会在这里抱怨:
// 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*)(&)), 1}’ is not a constant expression 12 | constexpr std::initializer_list broken { "but this doesn't?" }; |
令人困惑的是,为什么它可以毫无问题地构建第一个初始化列表。我在这里错过了什么?
这里的问题出在 broken
本身的初始化上。 std::initializer_list
是什么,它代表什么?它是一种引用类型(即以某种方式引用另一个对象),并且由 c 风格的数组支持。这个 c 风格数组的属性决定了 initializer_list 是否可以是一个 constexpr 变量。我们可以参考 [dcl.init.list] 了解这些属性。
5 An object of type
std::initializer_list<E>
is constructed from an initializer list as if the implementation generated and materialized a prvalue of type “array ofN
const E
”, whereN
is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and thestd::initializer_list<E>
object is constructed to refer to that array. [ Note: A constructor or conversion function selected for the copy shall be accessible in the context of the initializer list. — end note ] If a narrowing conversion is required to initialize any of the elements, the program is ill-formed. [ Example:struct X { X(std::initializer_list<double> v); }; X x{ 1,2,3 };
The initialization will be implemented in a way roughly equivalent to this:
const double __a[3] = {double{1}, double{2}, double{3}}; X x(std::initializer_list<double>(__a, __a+3));
assuming that the implementation can construct an
initializer_list
object with a pair of pointers. — end example ]6 The array has the same lifetime as any other temporary object, except that initializing an
initializer_list
object from the array extends the lifetime of the array exactly like binding a reference to a temporary. [ Example: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 };
For
v1
andv2
, theinitializer_list
object is a parameter in a function call, so the array created for{ 1, 2, 3 }
has full-expression lifetime. Fori3
, theinitializer_list
object is a variable, so the array persists for the lifetime of the variable. Fori4
, theinitializer_list
object is initialized in the constructor's ctor-initializer as if by binding a temporary array to a reference member, so the program is ill-formed ([class.base.init]). — end example ] [ Note: The implementation is free to allocate the array in read-only memory if an explicit array with the same initializer could be so allocated. — end note ]
所以这个数组就像任何其他由常量引用引用的临时对象一样。这意味着我们实际上可以将您的最小示例缩减为更小的东西
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 变量。这意味着它可以成功绑定。之后,它所绑定的东西必须在常量表达式中可用。无需对此过多赘述:由于在这种情况下临时变量具有完整的表达式生命周期(而不是延长的生命周期),因此它可用于常量表达式。