当前位置: 首页 > 知识库问答 >
问题:

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

林礼骞
2023-03-14

在以下代码中(在本地和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编译失败:

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'

(全部指包含括性词的行)

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(负责人)报告:

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>

为什么?

首先,它们显然都认为enum-id是非常数,尽管它们显然实际上是众所周知的编译时常数值。

其次,MSVC抱怨读取超出生存期,但group1的生存期及其值应在print中的整个使用过程中延长。

第三,gcc有一个奇怪的const-vs非const抱怨,我无法理解,因为初始化程序列表总是const。

最后,如果< code>constexpr被删除,除了gcc之外的所有人都将愉快地编译和运行这段代码,不会出现任何问题。虽然在这种情况下没有必要,但我看不出有什么好的理由让它不起作用。

同时,如果参数类型更改为st,gcc将仅编译和运行代码d::initializer_list

(有趣的是:gcc 8,随着参数类型的更改,确实成功编译并运行了代码,包括contexpr,其中gcc 9出错。

FWIW,将声明改为:

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

在所有三个编译器上编译和运行。因此,可能是initializer_list本身而不是枚举值行为不正常。但语法更烦人。(使用合适的make_array实现稍微不那么烦人,但我仍然不明白为什么原始版本是无效的。)

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

也可以,这要归功于C 17模板归纳。尽管现在print不能接受initializer_list;它必须基于通用容器/迭代器概念进行模板化,这很不方便。


共有2个答案

梁豪
2023-03-14

看来< code > STD::initializer _ list 还没有(在C 17中)满足文字类型的要求(这是< code>constexpr变量的类型必须满足的要求)。

关于在C14中是否这样做的讨论可以在本文中找到:为什么std::initializer_list没有定义为文字类型?这本身就是帖子讨论的后续内容声明constexpr initializer_list对象合法吗?

我将C14相关文章(C14标准)中的引文与最终工作草案(C17标准)进行了比较,它们是相同的。因此,没有明确要求std::initializer_list应该是文本类型。

引用C 17的最终工作草案(n4659):

[基本类型]/10.5

(10.5)一个可能的cv限定类类型(第12条),它具有以下所有属性:
(10.5.1)-它有一个普通的析构函数,
(10.5.2)-它要么是闭包类型(8.1.5.1),要么是聚合类型(11.6.1),或者至少有一个Constexpr构造函数或构造函数模板(可能从基类继承(10.3.3)),它不是复制或移动构造函数,
(10.5.3)-如果它是联合,它的至少一个非静态数据成员是非易失性文字类型,
(10.5.4)-如果它不是联合,它的所有非静态数据成员和基类都是非易失性文字类型。

[initializer_list.syn]/1

    < Li > initializer_list类型的对象提供对const E类型的对象数组的访问。[注意:一对指针或一个指针加上一个长度显然是initializer _ list的表示形式。initializer_list用于实现11.6.4中指定的初始值设定项列表。复制初始化列表不会复制底层元素。—结束注释]

这就是为什么宣布一个initializer_list对象是不合法的。

令狐泓
2023-03-14
匿名用户

初始化std::initializer_list时,会发生以下情况:

[已输入列表] (强调我的)

5一个对象的类型std:: initializer_列表是从一个初始化列表,如果实现生成和物化一个prvalue的类型"数组N const E",其中N是初始化列表中的元素数。该数组的每个元素被复制初始化与初始化列表的相应元素,和std:: initializer_列表对象被构造为引用该数组。[注意:为副本选择的构造函数或转换函数应在初始化列表的上下文中访问。-结束注意]如果缩小转换需要初始化任何元素,程序是病态的。[示例:

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对象。 — 结束示例 ]

如何使用临时数组来初始化< code > STD::initializer _ list 决定了< code>initializer_list是否用常量表达式初始化。最终,根据示例(尽管是非标准的),初始化将获取数组的地址,或者它的第一个元素,这将产生一个指针类型的值。这不是有效的常量表达式。

【expr.const】(强调矿山)

5常量表达式可以是一个glvalue核心常量表达式,它指的是常量表达式(定义如下)允许的结果实体,也可以是值满足以下约束的prvalue核心常数表达式:

  • 如果值是类类型的对象,则引用类型的每个非静态数据成员都引用一个实体,该实体是常量表达式的允许结果,
  • 如果值是指针类型,则它包含具有静态存储持续时间的对象的地址、此类对象结束后的地址([expr.add])、函数的地址或空指针值,以及
  • 如果该值是类或数组类型的对象,则每个子对象都满足该值的这些约束。

如果实体是具有静态存储持续时间的对象,而该对象不是临时对象,或者是值满足上述约束的临时对象,或是函数,则该实体是常量表达式的允许结果。

然而,如果数组是一个静态对象,那么初始化器将构成一个有效的常量表达式,可用于初始化< code>constexpr对象。由于< code > STD::initializer _ list 通过[dcl.init.list]/6对该临时对象具有生命期扩展的效果,所以当您将< code>group1声明为静态对象时,clang和gcc似乎也将该数组分配为静态对象,这使得初始化的格式良好性只受< code > STD::initializer _ list 是否为文字类型以及所使用的构造函数是否为< code>constexpr的影响。

最终,一切都有点模糊。

 类似资料:
  • 问题内容: 我有以下代码 我知道根据JLS,只允许将常量表达式作为批注属性的值。但为什么?如果数据类型匹配,为什么还不够?如果要在运行时对表达式进行求值,是否有可能出错?每个规范背后都有逻辑推理吗? 问题答案: 注释就像类型扩展或有关该类型的元数据。 因为Java是一种静态类型的语言(意味着类型在编译时是已知的),所以在编译时也知道注释属性数据(元数据)似乎是合理的- 您正在定义/声明有关注释(扩

  • 问题内容: 在Java 5及更高版本中,您具有foreach循环,该循环可以神奇地实现任何实现的对象: 但是,仍然没有实现,这意味着要迭代一个,您必须执行以下操作: 有谁知道为什么仍然不执行? 编辑: 为澄清起见,我不是在谈论枚举的语言概念,而是在Java API中称为“ 枚举 ” 的Java特定类。 问题答案: 枚举没有被修改为支持Iterable,因为它是一个接口,而不是一个具体的类(例如Ve

  • 问题内容: 我是一名即将毕业的计算机科学专业的学生,​​在我的整个编码生涯中,我发现很少使用枚举的实例,除了典型的情况(例如代表标准纸牌的面孔)外,还使用了枚举。 您是否知道在日常编码中使用枚举的任何巧妙方法? 为什么枚举如此重要,在什么情况下应该能够确定建立枚举是最佳方法? 问题答案: 这些是主要的论点,以及短的例子。 的情况 从Java 6开始,是一个凌乱类的示例,该类可以从使用中受益匪浅(除

  • 我听到一些人建议在C++中使用枚举类,因为它们的类型安全。 但这到底意味着什么呢?

  • 我想将枚举的所有可用值添加到消息中。但是我不能,因为它需要一个常量表达式。 结果错误: 如何做到这一点?

  • 问题内容: 我想知道为什么在Java语言中a 不能扩展。 我不是在谈论一个延伸的(这不能做,因为Java没有多重继承,而Š隐含延长),但一类的以只添加额外的方法,而不是额外的枚举值。 就像是: 要这样使用: 因此,有人可以对此限制提供理由(或将我指向正确的JLS部分)吗? 问题答案: 我认为 他们这样做 的答案来自以下问题: 在您的示例中,如何实例化MyClass?用户永远不会(通过)显式实例化枚