Is It Safe to Reinterpret_Cast an Enum Class Variable to a Reference of the Underlying Type

Is it safe to reinterpret_cast an enum class variable to a reference of the underlying type?

You might want to overload operator ++ for your enum if you really want to iterate its values:

Foo& operator++( Foo& f )
{
using UT = std::underlying_type< Foo >::type;
f = static_cast< Foo >( static_cast< UT >( f ) + 1 );
return f;
}

and use

for (Foo foo = Foo::First; foo != Foo::Last; ++foo)
{
...
}

To answer the question of whether or not the reinterpret_cast is allowed, it all starts with 5.2.10/1:

5.2.10 Reinterpret cast [expr.reinterpret.cast]

1 The result of the expression reinterpret_cast<T>(v) is the result of converting the expression v to type T. If T is an lvalue reference type or an rvalue reference to function type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue and the lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the expression v. Conversions that can be performed explicitly using reinterpret_cast are listed below. No other conversion can be performed explicitly using reinterpret_cast.

(emphasis mine)

The reinterpretation using references is based on pointers as per 5.2.10/11:

11 A glvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result refers to the same object as the source glvalue, but with the specified type. [ Note: That is, for lvalues, a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators (and similarly for reinterpret_cast<T&&>(x)). — end note ] No temporary is created, no copy is made, and constructors (12.1) or conversion functions (12.3) are not called.

Which transforms the question from this:

reinterpret_cast<int8_t&>(foo)

to whether this is legal:

*reinterpret_cast<int8_t*>(&foo)

Next stop is 5.2.10/7:

7 An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue v of type “pointer to T1” is converted to the type “pointer to cv T2”, the result is static_cast<cv T2*>(static_cast<cv void*>(v)) if both T1 and T2 are standard-layout types (3.9) and the alignment requirements of T2 are no stricter than those of T1, or if either type is void. Converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value. The result of any other such pointer conversion is unspecified.

Given 3.9/9 both int8_t and your enumeration type are standard layout types the question now transformed into:

*static_cast<int8_t*>(static_cast<void*>(&foo))

This is where you are out of luck. static_cast is defined in 5.2.9 and there is nothing which makes the above legal - in fact 5.2.9/5 is a clear hint that it is illegal. The other clauses don't help:

  • 5.2.9/13 requires T* -> void* -> T* where T must be identical (omitting cv)
  • 5.2.9/9 and 5.2.9/10 are not about pointers, but about values
  • 5.2.9/11 is about classes and class hierarchies
  • 5.2.9/12 is about class member pointers

My conclusion from this is that your code

reinterpret_cast<int8_t&>(foo)

is not legal, its behavior is not defined by the standard.

Also note that the above mentioned 5.2.9/9 and 5.2.9/10 are responsible for making the code legal which I gave in the initial answer and which you can still find at the top.

Using `reinterpret_cast` on an enum class - valid or undefined behavior?

The standard is a bit unclear:

3.10 Lvalues and rvalues [basic.lval]

10 If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:

[...]

(10.4) -- a type that is the signed or unsigned type corresponding to the dynamic type of the object,

[...]

This could be legitimately read as saying that the signed or unsigned type corresponding to an enumeration type is its underlying type, but I'd think this is meant to cover only accessing integer types through their other-signed corresponding type, that the underlying type of an enumeration type does not count as the (un)signed type corresponding to that enumeration type.

At least GCC agrees with this: it gives an aliasing warning for

enum E : int { };
int f(E e) { return *(int *) &e; }

warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]

strongly hinting that it will optimise on the assumption that no such aliasing takes place in your program.

Can an enum class be converted to the underlying type?

I think you can use std::underlying_type to know the underlying type, and then use cast:

#include <type_traits> //for std::underlying_type

typedef std::underlying_type<my_fields>::type utype;

utype a = static_cast<utype>(my_fields::field);

With this, you don't have to assume the underlying type, or you don't have to mention it in the definition of the enum class like enum class my_fields : int { .... } or so.

You can even write a generic convert function that should be able to convert any enum class to its underlying integral type:

template<typename E>
constexpr auto to_integral(E e) -> typename std::underlying_type<E>::type
{
return static_cast<typename std::underlying_type<E>::type>(e);
}

then use it:

auto value = to_integral(my_fields::field);

auto redValue = to_integral(Color::Red);//where Color is an enum class!

And since the function is declared to be constexpr, you can use it where constant expression is required:

int a[to_integral(my_fields::field)]; //declaring an array

std::array<int, to_integral(my_fields::field)> b; //better!

When to use reinterpret_cast?

The C++ standard guarantees the following:

static_casting a pointer to and from void* preserves the address. That is, in the following, a, b and c all point to the same address:

int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);

reinterpret_cast only guarantees that if you cast a pointer to a different type, and then reinterpret_cast it back to the original type, you get the original value. So in the following:

int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);

a and c contain the same value, but the value of b is unspecified. (in practice it will typically contain the same address as a and c, but that's not specified in the standard, and it may not be true on machines with more complex memory systems.)

For casting to and from void*, static_cast should be preferred.

(De)serializing an enum class

You could write a template function that will allow you to write 1 line for each operator>> that you define.

template <class UT, class S, class E> S& DeserializeEnumClassValue(S &s, E &e)
{
UT temp;
s >> temp;
e = static_cast<E>(temp);
return s;
}

And use it like that:

QDataStream &operator>>(QDataStream &stream, Type &type)
{
return DeserializeEnumClassValue<char>(stream, value);
}

But this can be improved using std::underlying_type (https://en.cppreference.com/w/cpp/types/underlying_type) as it is possible to get it at compile time.

If you take that approach, then you should also do something similar for operator<< to make maintenance easier.

Is it safe to convert a pointer to typed/sized enum to a pointer to the underlying type?

It is indeed safe (although I'm not a language-lawyer): What's stored in memory is a uint8_t, and that's what you'll be pointing at. However, if f() were to take a pointer to a non-const uint8_t, then it could potentially change the value to something that's not explicitly defined as one of the E enum values. (Edit:) While this is apparently allowed by the C++ standard, it is surprising to many (see discussion in the comments below on this point), and I would encourage you to ensure it does not happen.

... but as others suggest, you're not getting the error because of your notion of safety, but because implicit conversions aren't performed between pointed-to types. You can pass an E to a function taking a uint8_t, but not an E * to a function taking a uint8_t *; that would be - according to the language committee and in my opinion as well - an overly cavalier attitude to pointer types.

How to convert between int * and enum *?

Why is that?

From the compiler point of view int* and E* are pointers of different non-related types, that is why static_cast is not applicable here.

How would I convert a pointer to some int values to a pointer to int enums and vice versa?

You might try reinterpret_cast instead of static_cast:

f = reinterpret_cast<E *>(&i);
j = reinterpret_cast<int *>(&e);

From reinterpret_cast:

Any pointer to object of type T1 can be converted to pointer to object of another type cv T2

However, note, that dereferencing f or j (i.e. with *f or *j) will be a violation of the strict aliasing rule (for more details see the discussion below). This means that this kind of conversion, though strictly possible, is usually not useful.

Can I legally reinterpret_cast between layout-compatible standard-layout types?

but I can't see anything in the standard that therefore allows me to reinterpret_cast between them, even though that seems like the reasonable interpretation of "value representation". Is this technically allowed by the standard?

No. The standard is clear (see [basic.lval] p10) about which types can be aliased, and layout-compatible types are not included.

If not, what does knowing the value representation of a type give you?

If the types are both trivially copyable and have the same value representation then you could memcpy from an object of one type to an object of the other type, and vice versa. If they're not trivially copyable then it doesn't give you much at all.

AFAICT the standard doesn't actually say what can and can't be done with layout-compatible types.



Related Topics



Leave a reply



Submit