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 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:
- 5.2.9/13 requires
T*
->void*
->T*
whereT
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_cast
ing 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
Two Classes That Refer to Each Other
Open File with Fopen, Given Absolute Path on Windows
Get Signatures of Exported Functions in a Dll
Why "Not All Control Paths Return a Value" Is Warning and Not an Error
How to Read Frames from Videocapture from Secondary Webcam with Opencv
Sorting Std::Strings with Numbers in Them
Calling Base Class Definition of Virtual Member Function with Function Pointer
Aligning Static String Literals
Alpha Rendering Difference Between Opengl and Webgl
Abstract Class VS Interface in C++
How Copy from One Stringstream Object to Another in C++
Best Practices for Recovering from a Segmentation Fault
How to Make a C++ Struct Value-Initialize All Pod Member Variables
Large Negative Integer Literals