首页 > 解决方案 > C ++:无法从相同类型的常量初始化枚举值

问题描述

由于未知原因,我无法从值初始化枚举constexpr值。这是我的代码:

enum class Enum: unsigned int; //Forward declaration

constexpr Enum constant = static_cast<Enum>(2);

enum class Enum: unsigned int {
  A = 0,
  B = 1,
  C = B,                                   //This works
  D = constant,                            //This FAILS
  E = static_cast<unsigned int>(constant), //This works
  F = Enum::B                              //This works
};

我无法理解的是为什么我可以写C = B,但不能写D = constantB并且constant具有相同的类型!)

我仍然可以E = static_cast<unsigned int>(constant),但它太冗长了(在我的实际代码中,每个枚举值都是由constexpr函数调用初始化的,而且很难放在static_cast<unsigned int>任何地方)。

标签: c++enumsinitializationconstexprscoped-enums

解决方案


以下所有标准参考均指N4659:2017 年 3 月 Kona 后工作草案/C++17 DIS


C ++:无法从相同类型的常量初始化枚举值

首先,枚举类型和它的底层类型是不一样的,前者的枚举数应该用底层类型的constexpr值或隐式可转换为底层类型的常量表达式来定义。

正如[dcl.enum]/10的非规范示例中所述,范围枚举和整数之间没有隐式转换,甚至没有明确指定的固定基础类型:

枚举数或非范围枚举类型的对象的值通过整数提升转换为整数。[ <em>示例:[...]

请注意,没有为作用域枚举提供此隐式枚举到 int 的转换:

enum class Col { red, yellow, green };
int x = Col::red;               // error: no Col to int conversion
Col y = Col::red;
if (y) { }                      // error: no Col to bool conversion

— <em>结束示例]

并且,由于[conv.integral]/1[conv.prom]/4的规范文本中不存在范围枚举([conv.prom]/3用于其基础类型不固定的无范围枚举类型) ) [强调我的]:

[conv.integral]/1

整数类型的纯右值可以转换为另一种整数类型的纯右值。无作用域枚举类型的纯右值可以转换为整数类型的纯右值。

[conv.prom]/4

基础类型固定 ([dcl.enum])的无作用域枚举类型的纯右值可以转换为其基础类型的纯右值。此外,如果可以将整型提升应用于其基础类型,则其基础类型固定的无范围枚举类型的纯右值也可以转换为提升的基础类型的纯右值。

因此,您的程序,尤其是枚举定义D = constant,格式不正确。

实际上,如果我们修改您的示例,将Enum其更改为非范围枚举,则该程序不再是格式错误的。

enum Enum: unsigned int; //Forward declaration

constexpr Enum constant = static_cast<Enum>(2);

enum Enum: unsigned int {
  A = 0,
  B = 1,
  C = B,                                  // Ok
  D = constant,                           // Ok, implicit conversion
  E = static_cast<unsigned int>(constant) // Ok
};

int main() { }

那为什么C = B在我的代码中有效?

由于有些棘手的条款[dcl.enum]/5,它指出

在枚举说明符的右大括号之后,每个枚举数都有其 enumeration 的类型。如果基础类型是固定的,则右大括号之前的每个枚举数的类型都是基础类型[...]。如果基础类型不是固定的,则右大括号之前的每个枚举数的类型确定如下:

  • [...]

用外行的话来说,每个枚举器的类型基本上都会根据它是从枚举定义内部还是从枚举定义之外来看待而改变。

这意味着在枚举定义内部,我们可以在后面的枚举定义中使用先前定义的枚举数,因为它们都有类型(忽略[dcl.enum]/5.1[dcl.enum]/5.3中的一些细节)对于底层类型不固定的枚举),无论枚举是否有作用域(即不需要隐式转换),而在枚举的定义之外,这些枚举数与枚举的类型相同本身。

#include <type_traits>

enum class Enum: unsigned int; //Forward declaration

constexpr Enum constant = static_cast<Enum>(0);

// Forward a constexpr value whilst asserting
// type identity between two type template parameters.
template<typename T, typename U, unsigned int VALUE>
struct assert_and_get_value {
    static_assert(std::is_same_v<T, U>, "");
    static constexpr unsigned int value = VALUE;  
};

enum class Enum: unsigned int {
  A = 1,
  B,

  // C and B here are both of type 'unsigned int'
  C = B,
  D = assert_and_get_value<decltype(C), unsigned int, 5>::value,

  // Note that 'constant', however, in this scope, has type 'Enum'.
  E = assert_and_get_value<decltype(constant), const Enum, 6>::value,
};

// At this point, however, the type of each enumerator
// is the type of the enum.
static_assert(std::is_same_v<decltype(Enum::A), Enum>, "");
static_assert(!std::is_same_v<decltype(Enum::A), unsigned int>, "");

int main() {}

推荐阅读