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

时间:2017-08-03 12:44:04

标签: c++ c++11 casting language-lawyer

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));
}

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

#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);

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

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));
  }
}

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

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

然而,我对另一方面感兴趣。如果我转换为枚举并返回基础类型会发生什么?

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

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

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

枚举的范围对我来说并不清楚。根据7.2§7,如果枚举的基础类型是固定的,“枚举的值是基础类型的值”。因此,对于任何std::underlying_type<my_enumeration_type>,上述属性应保留。

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

1 个答案:

答案 0 :(得分:3)

标准似乎决定允许您使用给定类型的任意整数值作为固定到该类型的枚举的值,即使它们未被命名为枚举值。 5.2.9.10中的警告可能是为了限制没有固定底层类型的枚举。标准没有定义&#34;范围&#34;固定类型的枚举值与枚举值分开的任何值。特别是,它说:

  

可以定义一个枚举,该枚举的值不是由任何枚举器定义的。

所以&#34;在枚举值的范围内&#34;除了&#34;之外的其他任何东西都不能被理解为枚举值之一&#34;。对枚举值的范围没有其他定义。

因此,您可以安全地使用固定的基础类型进行枚举。对于无类型枚举,只有坚持安全位数才能安全。