代码之家  ›  专栏  ›  技术社区  ›  Miral

为什么枚举值的初始值设定项列表不被视为常量表达式?

  •  13
  • Miral  · 技术社区  · 7 年前

    在以下代码中(在本地和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'
    

    (均指包含 constexpr )

    Clang 8(负责人)报道:

    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有一个奇怪的常量对非常量的抱怨,我无法理解,因为初始化列表总是常量。

    最后,除了gcc之外,所有人都会很高兴地编译和运行这段代码,如果 常量表达式 被移除。当然不是 必需的 在这种情况下,但我看不出有什么好的理由让它不起作用。

    同时,只有当参数类型更改为时,gcc才会编译并运行代码 std::initializer_list<const Types> --做出这种更改会导致它无法在MSVC和clang中编译。

    (有趣的是:gcc 8在参数类型发生变化的情况下,确实成功编译并运行了代码,包括 常量表达式 ,其中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模板归纳,它也可以工作。虽然现在 打印 受不了 初始化列表 ; 它必须以通用容器/迭代器概念为模板,这很不方便。

    2 回复  |  直到 7 年前
        1
  •  10
  •   StoryTeller - Unslander Monica    7 年前

    当你初始化一个 std::initializer_list 事情是这样的:

    [dcl.init.list] (我的)

    5 构造了std::initializer_list类型的对象 从初始值设定项列表中 好像实现生成了 具体化了一个N常量数组类型的PR值 ,其中N是 初始值设定项列表中的元素数。该数组的每个元素 是否使用初始值设定项的相应元素初始化副本 并且std::initializer_list对象被构造为 参考那个数组。[  笔记 :构造函数或转换函数 选择的副本应可在 初始化列表。 —  尾注 ]如果需要缩小转换 要初始化任何元素,程序的格式都不正确。 [ 实例 :

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

    假设实现可以构造一个初始值设定项列表 对象和一对指针。 —  最后的例子 ]

    如何使用临时数组初始化 std::初始值设定项列表 是什么决定了 initializer_list 使用常量表达式初始化。最后,根据示例(尽管不是规范性的),初始化将采用数组的地址或其第一个元素,这将产生指针类型的值。这不是一个有效的常量表达式。

    [expr.const] (我的)

    5 A. 恒定表达式 是一个glvalue核心常量 表示实体的表达式,该实体是 常量表达式(定义见下文)或prvalue核心常量 其值满足以下约束的表达式:

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

    实体是常量表达式的允许结果,如果它是 对象的静态存储持续时间不是临时的 对象或是临时对象,其值满足上述条件 约束,或者它是一个函数。

    但是,如果数组是静态对象,那么该初始值设定项将构成一个有效的常量表达式,可用于初始化 constexpr 对象自从 std::初始值设定项列表 对那个临时的 [dcl.init.list]/6 ,当你 declare group1 as a static object 、clang和gcc似乎也将数组作为静态对象分配,这使得初始化的良好形式仅取决于是否 std::初始值设定项列表 是文本类型,所使用的构造函数是 常量表达式 .

    最终,一切都有点模糊。

        2
  •  7
  •   P.W    7 年前

    看来 std::initializer_list 尚未(在C++17中)满足 literal type (这是a型的要求 constexpr variable 必须满足)。

    关于它在C++14中是否会这样做的讨论可以在下面的帖子中找到: Why isn't std::initializer_list defined as a literal type? 这本身就是《华盛顿邮报》的后续报道 Is it legal to declare a constexpr initializer_list object?

    我将C++14相关文章(C++14标准)中提供的引文与最终工作草案(C++17标准)进行了比较,它们是相同的。 因此没有明确要求 std::初始值设定项列表 应该是文本类型。

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

    [basic.types]/10.5

    (10.5)具有所有 以下属性:
    (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

    1. initializer_list类型的对象提供对const E类型的对象数组的访问。[注意: 一对指针或一个指针 加上一个长度将是初始值设定项列表的明显表示形式。初始值设定项列表用于实现11.6.4中规定的初始值设定项列表。复制初始值设定项列表不会复制基础元素。[尾注]

    这就是为什么宣布一项协议不合法的原因 constexpr初始值设定项列表 对象