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 变量。这意味着它可以成功绑定。之后,它所绑定的东西必须在常量表达式中可用。无需对此过多赘述:由于在这种情况下临时变量具有完整的表达式生命周期(而不是延长的生命周期),因此它可用于常量表达式。