带有std :: initializer_list的怪异行为constexpr [英] Weird behaviour constexpr with std::initializer_list

查看:74
本文介绍了带有std :: initializer_list的怪异行为constexpr的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图理解为什么编译器在这里抱怨:

I am trying to understand why the compiler is complaining here:

// 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损坏{};|

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

令人困惑的是为什么它构造第一个初始化器列表时没有任何问题.我在这里想念什么?

It's confusing why it constructs the first initializer list without any issues. What am I missing here?

推荐答案

问题在于此处 broken 本身的初始化.什么是 std :: initializer_list ,它具有什么作用?它是一种引用类型(即以某种方式引用了另一个对象),并且由c样式数组支持.这个c样式数组的属性决定了initializer_list是否可以是constexpr变量.对于这些属性,我们可以咨询 [dcl.init.list] .

The problem is with the initialization of broken itself here. What is a std::initializer_list and what does it hold? It's a reference type (i.e. refers somehow to another objects), and it's backed by a c-style array. The properties of this c-style array are what determines if the initializer_list can be a constexpr variable. We can consult [dcl.init.list] for those properties.

5 类型为 std ::: initializer_list< E> 从初始化程序列表中,就好像实现已生成并实例化类型为 N const E 的数组"的prvalue,其中 N 为初始化程序列表中的元素数.那个的每个要素数组将使用的相应元素进行复制初始化.初始化程序列表,而 std ::: initializer_list< E> 对象是构造以引用该数组.[注意:构造函数或为副本选择的转换功能应可在初始化程序列表的上下文.—尾注]如果收窄需要转换才能初始化任何元素,程序格式不正确.[示例:

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));

假设实现可以构造一个 initializer_list 有一对指针的对象.—示例]

assuming that the implementation can construct an initializer_­list object with a pair of pointers.  — end example ]

6 数组具有相同的生存期和其他任何临时对象一样,除了从数组初始化 initializer_list 对象延长数组的寿命,就像将引用绑定到一个临时的.[示例:

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

对于 v1 v2 initializer_list 对象是函数调用,因此为 {1,2,3} 创建的数组具有完整的表达寿命.对于 i3 initializer_list 对象为一个变量,因此数组在变量的生存期内一直存在.对于 i4 initializer_list 对象在构造函数的ctor-initializer,就像通过将临时数组绑定到引用成员,因此程序格式不正确([class.base.init]).— end example] [注意:该实现可自由分配只读内存中的数组,如果显式数组具有相同的数组初始化程序可以这样分配.—尾注]

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 ]

因此,此数组就像常量引用所引用的任何其他临时对象一样.这意味着我们实际上可以将您的最小示例减少到更小

So this array is like any other temporary object that is referred to by a constant reference. Which means we can actually reduce your minimal example to something even smaller

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引用.就这么简单.

This produces the exact same behavior and error. We can pass 0 directly as an argument to the constexpr function, the reference binds, and we can even refer to it inside the function. However, a constexpr reference may not be initialized with 0. The reason for zero not being a valid initializer, is that it requires materializing a temporary int object. This temporary is not a static variable, and so may not be used to initialize a constexpr reference. Simple as that.

同样的理由也适用于您的情况.实现的临时数组不是具有静态存储持续时间的对象,因此它可能不能用于初始化constexpr引用类型.

The same reasoning applies to your case. The temporary array that's materialized is not an object with static storage duration, so it may not be used to initialize a constexpr reference type.

直接将参数传递给 test_cexpr 时起作用的原因是,相应的参数本身不是constexpr变量.这意味着它可以成功绑定.之后,它所绑定的东西必须在常量表达式中可用.在此无需赘述:由于在这种情况下,临时具有完整的表达生命周期(而不是延长生命周期),因此它可以在常量表达式中使用.

The reason it works when directly passing the argument to test_cexpr, is that the corresponding parameter is not itself a constexpr variable. Which means it can bind successfully. After which, the thing it's bound to just has to be usable in a constant expression. Without going into too much detail over this: since the temporary in that case has full-expression lifetime (and not lifetime extended), it is usable in a constant expression.

这篇关于带有std :: initializer_list的怪异行为constexpr的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆