简体   繁体   English

为什么枚举值的initializer_list不被视为常量表达式?

[英]Why is an initializer_list of enum values not considered a constant expression?

In the following code (tested locally and on Wandbox): 在以下代码中(在本地和Wandbox上测试):

#include <iostream>

enum Types
{
    A, B, C, D
};

void print(std::initializer_list<Types> types)
{
    for (auto type : types)
    {
        std::cout << type << std::endl;
    }
}

int main()
{
    constexpr auto const group1 = { A, D };
    print(group1);
    return 0;
}

MSVC 15.8.5 fails to compile with: MSVC 15.8.5无法编译:

error C2131: expression did not evaluate to a constant
note: failure was caused by a read of a variable outside its lifetime
note: see usage of '$S1'

(all referring to the line containing constexpr ) (均指包含constexpr的行)

Clang 8 (HEAD) reports: Clang 8(HEAD)报道:

error: constexpr variable 'group1' must be initialized by a constant expression
    constexpr auto const group1 = { A, D };
                         ^        ~~~~~~~~
note: pointer to subobject of temporary is not a constant expression
note: temporary created here
    constexpr auto const group1 = { A, D };
                                  ^

gcc 9 (HEAD) reports: gcc 9(HEAD)报告:

In function 'int main()':
error: 'const std::initializer_list<const Types>{((const Types*)(&<anonymous>)), 2}' is not a constant expression
   18 |     constexpr auto const group1 = { A, D };
      |                                          ^
error: could not convert 'group1' from 'initializer_list<const Types>' to 'initializer_list<Types>'
   19 |     print(group1);
      |           ^~~~~~
      |           |
      |           initializer_list<const Types>

Why? 为什么?

Firstly, they all apparently consider enum-ids to be non-constant, despite them obviously actually being well-known compile-time constant values. 首先,它们都显然认为enum-id是非常数的,尽管它们显然实际上是众所周知的编译时常量值。

Secondly, MSVC complains about read outside lifetime, but the lifetime of group1 and its values should extend throughout its usage in print . 其次,MSVC抱怨读取外部生命周期,但group1及其值的生命周期应该延伸到其在print使用。

Thirdly, gcc has a weird const-vs-non-const complaint that I can't make any sense of, since initialiser lists are always const. 第三,gcc有一个奇怪的const-vs-non-const投诉,我无法理解,因为初始化列表总是const。

Finally, all but gcc will happily compile and run this code without any problems if constexpr is removed. 最后,如果删除constexpr ,除了gcc之外的所有人都会愉快地编译并运行此代码而不会出现任何问题。 Granted it is not necessary in this case, but I can't see any good reason for it not to work. 当然,在这种情况下没有必要 ,但我看不出有任何不合理的理由。

Meanwhile gcc will only compile and run the code if the parameter type is changed to std::initializer_list<const Types> -- and making this change causes it to fail to compile in both MSVC and clang. 同时,如果参数类型更改为std::initializer_list<const Types> ,gcc将仅编译并运行代码 - 并且进行此更改会导致无法在MSVC和clang中编译。

(Interestingly: gcc 8, with the parameter type change, does successfully compile and run the code including constexpr , where gcc 9 errors out.) (有趣的是:gcc 8,参数类型更改,成功编译并运行包括constexpr的代码,其中gcc 9错误输出。)


FWIW, changing the declaration to this: FWIW,将声明改为:

    constexpr auto const group1 = std::array<Types, 2>{ A, D };

Does compile and run on all three compilers. 是否在所有三个编译器上编译和运行。 So it is probably the initializer_list itself that is misbehaving rather than the enum values. 所以initializer_list本身可能是行为不端而不是枚举值。 But the syntax is more annoying. 但语法更烦人。 (It's slightly less annoying with a suitable make_array implementation, but I still don't see why the original isn't valid.) (对于合适的make_array实现,它稍微不那么烦人,但我仍然不明白为什么原始无效。)


    constexpr auto const group1 = std::array{ A, D };

Also works, thanks to C++17 template induction. 由于C ++ 17模板归纳,也可以工作。 Though now print can't take an initializer_list ; 虽然现在print不能采用initializer_list ; it has to be templated on a generic container/iterator concept, which is inconvenient. 它必须在通用容器/迭代器概念上进行模板化,这是不方便的。

When you initialize a std::initializer_list it happens like this: 初始化std::initializer_list它会发生如下:

[dcl.init.list] (emphasis mine) [dcl.init.list] (强调我的)

5 An object of type std​::​initializer_list 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. 5 std :: initializer_list类型的对象是从初始化列表构造的, 就好像实现生成并实现了类型为“N const E的数组”的prvalue ,其中N是初始化列表中的元素数。 Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std​::​initializer_list object is constructed to refer to that array. 使用初始化列表的相应元素对该数组的每个元素进行复制初始化,并构造std :: initializer_list对象以引用该数组。 [ 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. 假设实现可以使用一对指针构造initializer_list对象。 end example ] - 结束例子 ]

How that temporary array gets used to initialize the std::initializer_list is what determines if the initializer_list is initialized with a constant expression. 那如何临时数组被用来初始化std::initializer_list是什么决定,如果initializer_list与常量表达式初始化。 Ultimately, according to example (despite being non-normative), that initialization is going to take the address of the array, or its first element, which will produce a value of a pointer type. 最后,根据示例(尽管是非规范性的),初始化将采用数组的地址或其第一个元素,这将产生指针类型的值。 And that is not a valid constant expression. 这不是一个有效的常量表达式。

[expr.const] (emphasis mine) [expr.const] (强调我的)

5 A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints: 5 常量表达式是glvalue核心常量表达式,它指的是一个实体,它是常量表达式的允许结果(如下所定义),或者是一个prvalue核心常量表达式,其值满足以下约束:

  • if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a constant expression, 如果值是类类型的对象,则引用类型的每个非静态数据成员引用一个实体,该实体是常量表达式的允许结果,
  • if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such an object ([expr.add]), the address of a function, or a null pointer value, and 如果值是指针类型,则它包含具有静态存储持续时间的对象的地址,超过此类对象末尾的地址([expr.add]),函数的地址或空指针值,以及
  • if the value is an object of class or array type, each subobject satisfies these constraints for the value. 如果值是类或数组类型的对象,则每个子对象都满足这些值的约束。

An entity is a permitted result of a constant expression if it is an object with static storage duration that is either not a temporary object or is a temporary object whose value satisfies the above constraints, or it is a function. 如果实体是具有静态存储持续时间的对象,则该实体是常量表达式的允许结果,该对象不是临时对象,或者是其值满足上述约束的临时对象,或者它是函数。

If the array was however a static object, then that initializer would constitute a valid constant expression that can be used to initialize a constexpr object. 但是,如果数组是静态对象,那么该初始化程序将构成一个有效的常量表达式,可用于初始化constexpr对象。 Since std::initializer_list has an effect of lifetime extension on that temporary by [dcl.init.list]/6 , when you declare group1 as a static object , clang and gcc seem allocate the array as a static object too, which makes the initialization's well-formedness subject only to whether or not std::initializer_list is a literal type and the constructor being used is constexpr . 由于std::initializer_list[dcl.init.list] / 6上具有生命周期延长效果,当你将group1声明为静态对象时 ,clang和gcc似乎也将数组分配为静态对象,这使得初始化的良构性仅受限于std::initializer_list是否为文字类型,而正在使用的构造函数是constexpr

Ultimately, it's all a bit murky. 最终,它有点模糊。

It appears std::initializer_list does not yet (in C++17) fulfill the requirements of literal type (which is a requirement the type of a constexpr variable has to satisfy). 似乎std::initializer_list还没有(在C ++ 17中)满足文字类型的要求(这是constexpr变量的类型必须满足的要求)。

A discussion on whether it does so in C++14 is found in this post: Why isn't std::initializer_list defined as a literal type? 关于它是否在C ++ 14中这样做的讨论可以在这篇文章中找到: 为什么std::initializer_list没有被定义为文字类型? which itself was a followup to the post discussing Is it legal to declare a constexpr initializer_list object? 这本身就是后期讨论的后续行为申报constexpr initializer_list对象是否合法?

I compared the citations provided in the C++14 related post (of the C++14 standard) to the final working draft (of the C++17 standard) and they are the same. 我将C ++ 14相关帖子(C ++ 14标准)中提供的引用与最终工作草案(C ++ 17标准)进行了比较,它们是相同的。 So there is no explicit requirement that std::initializer_list should be a literal type. 因此没有明确要求std::initializer_list应该是文字类型。

Citations from the final working draft of C++17 (n4659): 来自C ++ 17(n4659)最终工作草案的引文:

[basic.types]/10.5 [basic.types] /10.5

(10.5) a possibly cv-qualified class type (Clause 12) that has all of the following properties: (10.5)可能具有cv限定的类类型(第12条),它具有以下所有属性:
(10.5.1) — it has a trivial destructor, (10.5.1) - 它有一个简单的析构函数,
(10.5.2) — it is either a closure type (8.1.5.1), an aggregate type (11.6.1), or has at least one constexpr constructor or constructor template (possibly inherited (10.3.3) from a base class) that is not a copy or move constructor, (10.5.2) - 它是一个闭包类型(8.1.5.1),一个聚合类型(11.6.1),或者至少有一个constexpr构造函数或构造函数模板(可能从基类继承(10.3.3))那不是复制或移动构造函数,
(10.5.3) — if it is a union, at least one of its non-static data members is of non-volatile literal type, and (10.5.3) - 如果它是一个联合,它的至少一个非静态数据成员是非易失性文字类型,并且
(10.5.4) — if it is not a union, all of its non-static data members and base classes are of non-volatile literal types . (10.5.4) - 如果它不是联合,则其所有非静态数据成员和基类都是非易失性文字类型

[initializer_list.syn]/1 [initializer_list.syn] / 1

  1. An object of type initializer_list provides access to an array of objects of type const E. [ Note: A pair of pointers or a pointer plus a length would be obvious representations for initializer_list. 类型为initializer_list的对象提供对const E类型对象数组的访问。[注意: 一对指针或指针加上一个长度将是initializer_list的明显表示。 initializer_list is used to implement initializer lists as specified in 11.6.4. initializer_list用于实现11.6.4中指定的初始化列表。 Copying an initializer list does not copy the underlying elements. 复制初始化列表不会复制基础元素。 —end note ] - 尾注]

That is the reason why it is not legal to declare a constexpr initializer_list object. 这就是为什么声明constexpr initializer_list对象是不合法的原因。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM