带有std :: initializer_list的奇怪行为constexpr

时间:2019-12-31 04:13:42

标签: c++ language-lawyer constexpr initializer-list string-literals

也许这里只是程序员,但我试图理解为什么编译器在这里抱怨。

// 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?" };
      |

1 个答案:

答案 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
};
     

对于v1v2initializer_­list对象是   函数调用,因此为{ 1, 2, 3 }创建的数组具有   全表达寿命。对于i3initializer_­list对象是   一个变量,因此数组在变量的生存期内一直存在。   对于i4initializer_­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变量。这意味着它可以成功绑定。之后,它所绑定的东西必须可以在常量表达式中使用。在此无需赘述:由于在这种情况下,临时项具有完整的表达式生存期(而不是生存期的延长),因此可以在常量表达式中使用。