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

C++: Cannot initialize enum value from a constant of the same type

由于未知原因,我无法从 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 = constantBconstant是同一个类型!)

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

以下所有标准参考均指N4659: March 2017 post-Kona working draft/C++17 DIS


C++: Cannot initialize enum value from a constant of the same type

首先,枚举类型和它的底层类型不一样,枚举[前者的 =54=] 应定义为基础类型的 constexpr 值(如果有)或常量表达式 可隐式转换 为基础类型。

[dcl.enum]/10 的 non-normative 示例所述,在 作用域 枚举和整数之间没有隐式转换,甚至没有显式转换指定的固定基础类型:

The value of an enumerator or an object of an unscoped enumeration type is converted to an integer by integral promotion. [ Example: [...]

Note that this implicit enum to int conversion is not provided for a scoped enumeration:

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

— end example ]

并且,根据 [conv.integral]/1 and [conv.prom]/4 ([conv.prom]/3 的规范文本中范围枚举的 non-presence 对其基础类型不固定的无范围枚举类型的约束)[强调 我的]:

[conv.integral]/1

A prvalue of an integer type can be converted to a prvalue of another integer type. A prvalue of an unscoped enumeration type can be converted to a prvalue of an integer type.

[conv.prom]/4

A prvalue of an unscoped enumeration type whose underlying type is fixed ([dcl.enum]) can be converted to a prvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type.

因此,您的程序,尤其是枚举定义 D = constant,是 ill-formed。

事实上,如果我们修改您的示例,将 Enum 更改为 non-scoped 枚举,程序将不再是 ill-formed.

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() { }

Then why C = B in my code works?

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

Following the closing brace of an enum-specifier, each enumerator has the type of its enumeration. If the underlying type is fixed, the type of each enumerator prior to the closing brace is the underlying type [...]. If the underlying type is not fixed, the type of each enumerator prior to the closing brace is determined as follows:

  • [...]

通俗地说,每个枚举器的类型基本上会根据它是从枚举定义内部还是外部查看而改变。

这意味着在枚举定义内部,我们可以在后面的定义中使用先前定义的枚举器,因为它们都具有类型(忽略 [dcl.enum]/5.1 and [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() {}

我觉得这很合乎逻辑。

这里有两种实体。

  1. Class 是枚举
  2. 它的内部类型

B和C都是第二种,所以你可以将一个分配给另一个。 另一方面,D 是第二种,constant 是第一种。并且 enum class 概念旨在防止从枚举到其基础类型的隐式转换,因此您的编译错误。