我是否可以始终安全地转换为固定(范围)枚举的基础类型?

Zet*_*eta 10 c++ casting language-lawyer c++11

TL; DR:以下是否始终安全?或者它是否导致未定义,未指定或实现定义的行为?

template <class T> 
using ut = typename std::underlying_type<T>::type;

template <typename E> ut<E> identity(ut<E> value) {
  return static_cast<ut<E>>(static_cast<E>(value));
}
Run Code Online (Sandbox Code Playgroud)

如果我有一个范围的枚举,我总是可以将它转换为基础类型:

#include <cassert>             // if you want to follow along
#include <initializer_list>    // copy everything and remove my text

enum class priority : int { 
  low = 0, 
  normal = 1,
  high = 2 
};

// works fine
int example = static_cast<int>(priority::high);
Run Code Online (Sandbox Code Playgroud)

对于枚举中定义的所有值,我也可以期望得到值:

constexpr priority identity_witness(priority p) {
  return static_cast<priority>(static_cast<int>(p));
}

void test_enum() {
  for (const auto p : {priority::low, priority::normal, priority::high}) {
    assert(p == identity_witness(p));
  }
}
Run Code Online (Sandbox Code Playgroud)

根据N3337(C++ 11),5.2.9 Static cast [expr.static.cast]§9-10这很好:

  1. 可以将范围枚举类型(7.2)的值显式转换为整数类型.如果原始值可以由指定的类型表示,则该值不变....
  2. 可以将整数或枚举类型的值显式转换为枚举类型.如果原始值在枚举值(7.2)的范围内,则该值不变....

但是,我对另一种方式感兴趣.如果我转换为枚举并返回基础类型会发生什么?

constexpr int identity_witness(int i) {
  return static_cast<int>(static_cast<priority>(i));
}

void test_int() {
  for (const auto p : {0, 1, 2, 3, 4, 5}) {
    assert(p == identity_witness(p));
  }
}

int main() {
  test_enum();
  test_int();
}
Run Code Online (Sandbox Code Playgroud)

这编译并且工作正常,因为static_cast底层类型根本不会改变内存(可能).但是,标准表示如果值不在以下范围内,则行为未指定:

  1. [续]否则,结果值未指定(可能不在该范围内).

我不清楚这些枚举的范围.根据7.2§7,如果枚举的基础类型是固定的,"枚举的值是基础类型的值".因此,对于任何人std::underlying_type<my_enumeration_type>,上述财产应该成立.

这个参数是否成立,或者我是否遗漏了标准中的一些奇怪的子句,以便对枚举的基础类型进行强制转换可能会导致未定义或未指定的行为?

Sne*_*tel 3

该标准似乎决定允许您使用给定类型的任意整数值作为固定到该类型的枚举的值,即使它们没有命名为枚举值。5.2.9.10 中的警告可能是为了限制没有固定基础类型的枚举。该标准没有将固定类型枚举值的“范围”定义为与枚举值分开的任何内容。它特别指出:

可以定义一个具有未由任何枚举器定义的值的枚举。

因此,“在枚举值的范围内”不能理解为“是枚举值之一”之外的任何内容。枚举值的范围没有其他定义。

因此,对于具有固定基础类型的枚举是安全的。对于无类型枚举,只有坚持安全的位数才是安全的。