TL;DR: Is the following always safe? Or does it lead to undefined, unspecified or implementation defined behaviour?
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));
}
If I have a scoped enumeration I can always cast it into the underlying type:
#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);
For all values that are defined in the enumeration I can also expect that I get the value back:
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));
}
}
According to N3337 (C++11), 5.2.9 Static cast [expr.static.cast] § 9-10 this is fine:
- A value of a scoped enumeration type (7.2) can be explicitly converted to an integral type. The value is unchanged if the original value can be represented by the specified type. …
- A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2). …
However, I'm interested in the other way round. What happens if I cast to an enum and back to the underlying type?
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();
}
This compiles and works fine, since a static_cast
to the underlying type won't change the memory at all (probably). However, the standard says that the behaviour is unspecified if the value is not in the range:
- [continued] Otherwise, the resulting value is unspecified (and might not be in that range).
The range of the enumerations isn't clear to me. According to 7.2§7, "the values of the enumeration are the values of the underlying type" if the enumeration's underlying type is fixed. Therefore, for any std::underlying_type<my_enumeration_type>
, the property above should hold.
Does this argument hold, or did I miss some strange clause in the standard so that a cast into the underlying type of the enumeration might lead to undefined or unspecified behaviour?
Static Cast: This is the simplest type of cast which can be used. It is a compile time cast.It does things like implicit conversions between types (such as int to float, or pointer to void*), and it can also call explicit conversion functions (or implicit ones).
As we learnt in the generic types example, static_cast<> will fail if you try to cast an object to another unrelated class, while reinterpret_cast<> will always succeed by "cheating" the compiler to believe that the object is really that unrelated class.
The static_cast operator can be used for operations such as converting a pointer to a base class to a pointer to a derived class. Such conversions are not always safe.
In an unscoped enum, the scope is the surrounding scope; in a scoped enum, the scope is the enum-list itself. In a scoped enum, the list may be empty, which in effect defines a new integral type. class. By using this keyword in the declaration, you specify the enum is scoped, and an identifier must be provided.
The standard seems determined to allow you to use arbitrary integral values of a given type as values for an enumeration fixed to that type, even if they're not named as enumeration values. The caveat in 5.2.9.10 is presumably meant to limit enumerations without a fixed underlying type. The standard doesn't define the "range" of a fixed-type enumeration's values as anything separate from the enumeration's values. In particular, it says:
It is possible to define an enumeration that has values not defined by any of its enumerators.
So "is within the range of the enumeration values" cannot be understood as anything other than "is one of the enumeration values". There's no other definition of the range of an enumeration's values.
So you're safe for enumerations with a fixed underlying type. For untyped enumerations, you're only safe if you stick to the safe number of bits.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With