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 это хорошо:
- Значение типа нумерации (7.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
для базового типа вообще не изменяет память (возможно). Однако в стандарте указано, что поведение не указано, если значение не находится в диапазоне:
- [continue] В противном случае результирующее значение не указано (и может быть не в этом диапазоне).
Диапазон перечислений мне непонятен. Согласно 7.2§7, "значения перечисления являются значениями базового типа", если фиксированный тип перечисления является фиксированным. Поэтому для любого std::underlying_type<my_enumeration_type>
должно выполняться свойство выше.
Сохраняется этот аргумент или я пропустил какое-то странное предложение в стандарте, так что приведение в базовый тип перечисления может привести к undefined или неуказанному поведению?