Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I've seen reinterpret_cast used to apply incrementation to enum classes, and I'd like to know if this usage is acceptable in standard C++.

enum class Foo : int8_t
{
    Bar1,
    Bar2,
    Bar3,
    Bar4,

    First = Bar1,
    Last = Bar4
};

for (Foo foo = Foo::First; foo <= Foo::Last; ++reinterpret_cast<int8_t &>(foo))
{
    ...
}

I know casting to a reference of a base class is safe in case of trivial classes. But since enum classes are not event implicitly converted to their underlying types, I'm not sure if and how the code above would be guaranteed to work in all compilers. Any clues?

like image 545
GOTO 0 Avatar asked Oct 20 '13 11:10

GOTO 0


People also ask

Is it safe to use reinterpret_cast?

reinterpret_cast is a very special and dangerous type of casting operator. And is suggested to use it using proper data type i.e., (pointer data type should be same as original data type).

Is reinterpret cast compile time?

The dynamic cast is the only that needs to be "calculated" in run-time. All other casts are calculated in compile-time. The machine code for a static_cast is a fixed function based on the type you are casting FROM and TO. For reinterpret_cast , the machine code can be resolved in compile-time as well.


1 Answers

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.

like image 124
Daniel Frey Avatar answered Sep 26 '22 02:09

Daniel Frey