首页 > 解决方案 > 为什么枚举值的 initializer_list 不被视为常量表达式?

问题描述

在以下代码中(在本地和 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 (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) 报告:

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-ids 是非常量的,尽管它们显然实际上是众所周知的编译时常量值。

其次,MSVC 抱怨 read outside 生命周期,但生命周期group1及其值应该在print.

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

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

同时 gcc 只会在参数类型更改为std::initializer_list<const Types>-- 的情况下编译和运行代码,并且进行此更改会导致它在 MSVC 和 clang 中都无法编译。

(有趣的是:gcc 8,随着参数类型的改变,成功编译并运行了包括 在内的代码constexpr,其中 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; 它必须在通用容器/迭代器概念上进行模板化,这很不方便。

标签: c++language-lawyerc++17constexpr

解决方案



推荐阅读