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

问题描述

由于未知原因,我无法从<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous"></script>值初始化枚举值。这是我的代码

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 = BD = constantB具有相同的类型!)

我仍然可以做constant,但是它太冗长了(在我的实际代码中,每个枚举值都是通过E = static_cast<unsigned int>(constant)函数调用初始化的,很难放置{{1} })。

解决方法

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


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

首先,枚举类型和它的底层类型是不相同的,并且前者的枚举器应定义为(如果有的话)是基础类型的constexpr值,或者是可以隐式转换为基础类型的常量表达式。

[dcl.enum]/10的非规范示例所述,在作用域枚举和整数之间没有隐式转换,甚至没有隐式转换为它明确指定的固定基础类型:>

未枚举类型的枚举数或对象的值通过整数提升转换为整数。 [示例: [...]

请注意,此范围内的枚举未提供此隐式的枚举到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

结束示例]

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

[conv.integral] / 1

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

[conv.prom] / 4

其基本类型为固定的([dcl.enum])不受限制的枚举类型的prvalue可以转换为其基本类型的prvalue 。此外,如果可以将积分提升应用于其基础类型,则基础类型固定的无范围枚举类型的prvalue也可以转换为提升的基础类型的prvalue。

因此,您的程序(尤其是枚举定义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,其中指出

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

  • [...]

用通俗易懂的话来说,每个枚举器的类型基本上都根据从枚举定义内部还是外部枚举而改变。

这意味着在枚举定义内部,我们可以在随后的定义中使用先前定义的枚举器,因为它们都具有类型(忽略[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,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. 类被枚举
  2. 其内部类型

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