Подтвердить что ты не робот

Могу ли я всегда безопасно использовать базовый тип фиксированного (скопированного) перечисления?

TL; DR: Безопасно ли следующее? Или это приводит к undefined, неопределенному или определенному поведению реализации?

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

Это компилируется и работает отлично, так как static_cast для базового типа вообще не изменяет память (возможно). Однако в стандарте указано, что поведение не указано, если значение не находится в диапазоне:

  1. [continue] В противном случае результирующее значение не указано (и может быть не в этом диапазоне).

Диапазон перечислений мне непонятен. Согласно 7.2§7, "значения перечисления являются значениями базового типа", если фиксированный тип перечисления является фиксированным. Поэтому для любого std::underlying_type<my_enumeration_type> должно выполняться свойство выше.

Сохраняется этот аргумент или я пропустил какое-то странное предложение в стандарте, так что приведение в базовый тип перечисления может привести к undefined или неуказанному поведению?

4b9b3361

Ответ 1

Стандарт, как представляется, позволяет вам использовать произвольные интегральные значения данного типа в качестве значений для перечисления, привязанного к этому типу, даже если они не называются значениями перечисления. Оговорка в 5.2.9.10 предположительно предназначена для ограничения перечислений без фиксированного базового типа. Стандарт не определяет "диапазон" значений перечисления фиксированного типа как что-либо отдельно от значений перечисления. В частности, он говорит:

Можно определить перечисление, которое имеет значения, не определенные каким-либо из его счетчиков.

Итак, "находится в пределах диапазона значений перечисления", нельзя понимать как нечто иное, чем "одно из значений перечисления". Нет другого определения диапазона значений перечисления.

Итак, вы безопасны для перечислений с фиксированным базовым типом. Для нетипизированных перечислений вы будете в безопасности, если будете придерживаться безопасного количества бит.