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!
Why do I have to explicitly cast an enum that I have specified the underlying type for?
This is a type safety feature. Implicit conversions have a long history of associated issues in C++, have a look at Stephen Dewhurst's book "C++ Gotchas" - the longest chapter is that on conversions.
Recall that an enumeration is nothing but a way to bind a constant value to a comprehensible name. This way, there is a finite, documented set of possible values, and by an implicit conversion, you massively extend the domain without giving any notice. Example:
void f(uint16_t arg); // arg can have 65536 different values
enum class Apples : uint16_t { GRANNY_SMITH = 1 }; // one possible value
If this compiles:
f(Apples::GRANNY_SMITH);
you have just given up the restrictiveness without giving notice. Instead,
f(static_cast<uint16_t>(Apples::GRANNY_SMITH));
is clearer and uglier. The clumsy nature of the cast tells you "why do you even do it?", and answering this question shouldn't be too easy. You created the enumeration to refer to possible values by typing out their associated names, right?
How to automatically convert strongly typed enum into int?
Strongly typed enums aiming to solve multiple problems and not only scoping problem as you mentioned in your question:
- Provide type safety, thus eliminating implicit conversion to integer by integral promotion.
- Specify underlying types.
- Provide strong scoping.
Thus, it is impossible to implicitly convert a strongly typed enum to integers, or even its underlying type - that's the idea. So you have to use static_cast
to make conversion explicit.
If your only problem is scoping and you really want to have implicit promotion to integers, then you better off using not strongly typed enum with the scope of the structure it is declared in.
C-style cast of enum class to reference of underlying type char
As has been noted in comments, there is no strict aliasing violation because char
can alias any type.
In practice I doubt any real compiler would do anything other than the "obvious" implementation, i.e. give the enum the same size and representation as the underlying type. In which case your reinterpret_cast
would be well-defined and behave as expected.
However the Standard (as of C++17) does not appear to guarantee that.
As far as I can see, it only specifies that any value of the underlying type can be stored in the enum object, and that static_cast
can be used losslessly for values in the range of the underlying type.
There is a language lawyer question here about whether sizeof(ECode) == sizeof(char)
must hold, although the answers seem to say "the standard doesn't actually say so but they probably meant it".
But even if the size is the same there isn't a representation guarantee, e.g. the bits could be stored in some different order and the static_cast
transforms the bits.
In [basic.fundamental] where it specifies the representation of integer types, it even has an explicit footnote saying that enumerations are not integer types.
Why can't C++11 strongly-typed enum be cast to underlying type via pointer?
TL;DR: The designers of C++ don't like type punning.
Others have pointed out why it's not allowed by the standard; I will try to address why the writers of the standard might have made it that way. According to this proposal, the primary motivation for strongly-typed enums was type safety. Unfortunately, type safety means many things to many people. It's fair to assume consistency was another goal of the standards committee, so let's examine type safety in other relevant contexts of C++.
C++ type safety
In C++ in general, types are unrelated unless explicitly specified to be related (through inheritance). Consider this example:
class A
{
double x;
int y;
};
class B
{
double x;
int y;
};
void foo(A* a)
{
B* b = static_cast<B*>(a); //error
}
Even though A and B have the exact same representation (the standard would even call them "standard-layout types"), you cannot convert between them without a reinterpret_cast
. Similarly, this is also an error:
class C
{
public:
int x;
};
void foo(C* c)
{
int* intPtr = static_cast<int*>(c); //error
}
Even though we know the only thing in C is an int and you can freely access it, the static_cast
fails. Why? It's not explicitly specified that these types are related. C++ was designed to support object-oriented programming, which provides a distinction between composition and inheritance. You can convert between types related by inheritance, but not those related by composition.
Based on the behavior you've seen, it's clear strongly-typed enums are related by composition to their underlying types. Why might this have been the model the standard committee chose?
Composition vs Inheritance
There are many articles on this issue better written than anything I could fit here, but I'll attempt to summarize. When to use composition vs. when to use inheritance is certainly a grey area, but there are many points in favor of composition in this case.
- Strongly-typed enums are not intended to be used as integral values. Thus the 'is-a' relationship indicated by inheritance does not fit.
- On the highest level, enums are meant to represent a set of discrete values. The fact that this is implemented through assigning an id number to each value is generally not important (unfortunately C exposes and thus enforces this relationship).
- Looking back at the proposal, the listed reason for allowing a specified underlying type is to specify the size and signedness of the enum. This is much more of an implementation detail than an essential part of the enum, again favoring composition.
You could argue for days about whether or not inheritance or composition is better in this case, but ultimately a decision had to be made and the behavior was modeled on composition.
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.
static_cast to enum class from underlying type value and switch for compiler assistance
It is legal C++.
The drawback is the DRY violation, but avoiding it is difficult.
In c++23 we'll have reflection and be able to generate equivalent code (without having to rely on compiler warnings to make sure we didn't miss any). Reflection syntax is still a bit in flux, but every version I have read over was able to handle that problem.
Related Topics
"String Could Not Resolved" Error in Eclipse for C++ (Eclipse Can't Resolve Standard Library)
Passing a Variable as a Template Argument
Order of Evaluation of Elements in List-Initialization
How to Add a Library Path in Cmake
Random Number C++ in Some Range
Error: Invalid Operands of Types 'Const Char [35]' and 'Const Char [2]' to Binary 'Operator+'
How to Use Polymorphic Attributes with Boost::Spirit::Qi Parsers
C++17 Class Template Partial Deduction
How to Build and Use Google Tensorflow C++ API
Matching Alias Template as Template Argument
What's the Behavior of an Uninitialized Variable Used as Its Own Initializer
Passing Arguments to Std::Async by Reference Fails
Gcc C++ "Hello World" Program -> .Exe Is 500Kb Big When Compiled on Windows. How to Reduce Its Size
Is There Any Case Where a Return of a Rvalue Reference (&&) Is Useful