Ant*_*nov 5 c++ enums initialization constexpr scoped-enums
由于未知原因,我无法从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
};
Run Code Online (Sandbox Code Playgroud)
我无法理解的是为什么我可以写C = B,但不能写D = constant(B并且constant具有相同的类型!)
我仍然可以做E = static_cast<unsigned int>(constant),但它太冗长了(在我现实生活中的代码中,每个枚举值都是由constexpr函数调用初始化的,很难放在static_cast<unsigned int>任何地方)。
以下所有标准参考均指N4659:2017 年 3 月后 Kona 工作草案/C++17 DIS。
C++:无法从相同类型的常量初始化枚举值
首先,枚举类型和它的底层类型是不一样的,前者的枚举器如果有底层类型的constexpr值或隐式转换为底层类型的常量表达式,则应定义为枚举类型。
如[dcl.enum]/10的非规范示例中所述,范围枚举和整数之间没有隐式转换,甚至没有到其明确指定的固定基础类型:
枚举数或无作用域枚举类型的对象的值通过整数提升转换为整数。[? 示例: [...]
请注意,不为作用域枚举提供此隐式枚举到 int 转换:
Run Code Online (Sandbox Code Playgroud)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用于基础类型不固定的无范围枚举类型) [强调我的]:
[转换积分]/1
整数类型的纯右值可以转换为另一种整数类型的纯右值。无作用域枚举类型的纯右值可以转换为整数类型的纯右值。
[转换.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() { }
Run Code Online (Sandbox Code Playgroud)
那为什么
C = B在我的代码中有效呢?
由于有点棘手的条款[dcl.enum]/5,它指出
在枚举说明符的右大括号之后,每个枚举器都有其枚举的类型。如果基础类型是固定的,则右大括号之前的每个枚举数的类型是基础类型[...]。如果基础类型不是 fixed,则右大括号之前的每个枚举数的类型确定如下:
- [...]
通俗地说,每个枚举器的类型基本上会根据它是从枚举的定义内还是从枚举的定义之外查看而发生变化。
这意味着在枚举定义内部,我们可以在后面的定义中使用先前定义的枚举器,因为它们都有类型(忽略[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() {}
Run Code Online (Sandbox Code Playgroud)