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++.
enu
The increment accesses the value of foo
through an lvalue of a different type, which is undefined behaviour except in the cases listed in 3.10 [basic.lval]. Enumeration types and their underlying types are not in that list, so the code has undefined behaviour.
With some compilers that support non-standard extensions you can do it via type-punning:
union intenum
{
int8_t i;
Foo e;
};
intenum ie;
for (ie.e = Foo::First; ie.e <= Foo::Last; ++ie.i)
// ...
but this is not portable either, because accessing intenum::i
after storing a value in intenum::e
is not allowed by the standard.
But why not just use an integer and convert as needed?
for (int8_t i = static_cast<int8_t>(Foo::First);
i <= static_cast<int8_t>(Foo::Last);
++i)
{
Foo e = static_cast<Foo>(i);
// ...
}
This is portable and safe.
(It's still not a good idea IMHO, because there could be several enumerators with the same value, or values of the enumeration type that have no corresponding enumerator label.)
It is safe as long as it casts to the exact underlying type of the enum.
If the underlying type of the enum class changes that ++reinterpret_cast<int8_t &>(foo)
breaks silently.
A safer version:
foo = static_cast<Foo>(static_cast<std::underlying_type<Foo>::type>(foo) + 1);
Or,
++reinterpret_cast<std::underlying_type<Foo>::type&>(foo);
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 expressionv
to typeT
. IfT
is an lvalue reference type or an rvalue reference to function type, the result is an lvalue; ifT
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 expressionv
. Conversions that can be performed explicitly usingreinterpret_cast
are listed below. No other conversion can be performed explicitly usingreinterpret_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 toT2
” if an expression of type “pointer toT1
” can be explicitly converted to the type “pointer toT2
” using areinterpret_cast
. The result refers to the same object as the source glvalue, but with the specified type. [ Note: That is, for lvalues, a reference castreinterpret_cast<T&>(x)
has the same effect as the conversion*reinterpret_cast<T*>(&x)
with the built-in&
and*
operators (and similarly forreinterpret_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 toT1
” is converted to the type “pointer to cvT2
”, the result isstatic_cast<
cv
T2*>(static_cast<
cv
void*>(v))
if bothT1
andT2
are standard-layout types (3.9) and the alignment requirements ofT2
are no stricter than those ofT1
, or if either type isvoid
. Converting a prvalue of type “pointer toT1
” to the type “pointer toT2
” (whereT1
andT2
are object types and where the alignment requirements ofT2
are no stricter than those ofT1
) 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:
T*
-> void*
-> T*
where T
must be identical (omitting cv)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.