Enum vs Strongly typed enum
OK, first example: old-style enums do not have their own scope:
enum Animals {Bear, Cat, Chicken};
enum Birds {Eagle, Duck, Chicken}; // error! Chicken has already been declared!
enum class Fruits { Apple, Pear, Orange };
enum class Colours { Blue, White, Orange }; // no problem!
Second, they implicitly convert to integral types, which can lead to strange behaviour:
bool b = Bear && Duck; // what?
Finally, you can specify the underlying integral type of C++11 enums:
enum class Foo : char { A, B, C};
Previously, the underlying type was not specified, which could cause compatibility problems between platforms. Edit It has been pointed out in comments that you can also specify the underlying integral type of an "old style" enum in C++11.
strongly typed enum - `class` keyword optional?
Since c++11
even normal enums (which still exist) can accept an underlying type specification. See here.
enum-key attr(optional) identifier(optional) enum-base(optional)(C++11) { enumerator-list(optional) }
Emphasis mine
So it is a normal enum
not a enum class
but with a enum-base
specification.
Why is enum class preferred over plain enum?
C++ has two kinds of enum
:
enum class
es- Plain
enum
s
Here are a couple of examples on how to declare them:
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // plain enum
What is the difference between the two?
enum class
es - enumerator names are local to the enum and their values do not implicitly convert to other types (like anotherenum
orint
)Plain
enum
s - where enumerator names are in the same scope as the enum and their
values implicitly convert to integers and other types
Example:
enum Color { red, green, blue }; // plain enum
enum Card { red_card, green_card, yellow_card }; // another plain enum
enum class Animal { dog, deer, cat, bird, human }; // enum class
enum class Mammal { kangaroo, deer, human }; // another enum class
void fun() {
// examples of bad use of plain enums:
Color color = Color::red;
Card card = Card::green_card;
int num = color; // no problem
if (color == Card::red_card) // no problem (bad)
cout << "bad" << endl;
if (card == Color::green) // no problem (bad)
cout << "bad" << endl;
// examples of good use of enum classes (safe)
Animal a = Animal::deer;
Mammal m = Mammal::deer;
int num2 = a; // error
if (m == a) // error (good)
cout << "bad" << endl;
if (a == Mammal::deer) // error (good)
cout << "bad" << endl;
}
Conclusion:
enum class
es should be preferred because they cause fewer surprises that could potentially lead to bugs.
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.
c++ conversion of strongly typed enums
Define the unary +
operator to perform conversion to integer type.
enum RiskLevel { None, Warn, Low, High, Critical };
auto operator + ( RiskLevel value )
{ return std::underlying_type_t< RiskLevel >( value ); }
void logStuff( RiskLevel rl ) {
stringstream ss;
ss << + rl;
LOG(s);
}
void compareEnum( RiskLevel rl ) {
if ( + rl > + RiskLevel::Low ) {
...
}
}
More depth in this answer.
Why can a strongly-typed enum be initialized with an integer without static_cast?
Looking at the reference using list intializers is allowed since C++17:
Both scoped enumeration types and unscoped enumeration types whose
underlying type is fixed can be initialized from an integer without a
cast, using list initialization, if all of the following is true:
- the initialization is direct-list-initialization
- the initializer list has only a single element
- the enumeration is either scoped or unscoped with underlying type fixed
- the conversion is non-narrowing
Clang supports this since version 3.9 (according to the implementation status page)
GCC supports this since version 7 (according to the standards support page)
See this C++ proposal for additional context and motivation: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0138r2.pdf
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.
Related Topics
Opencv Unable to Set Up Svm Parameters
Why Callback Functions Needs to Be Static When Declared in Class
Best Introduction to C++ Template Metaprogramming
Difference Between Std::Result_Of and Decltype
App Does Not Run with VS 2008 Sp1 Dlls, Previous Version Works with Rtm Versions
How Large Is a Dword with 32- and 64-Bit Code
What's the Difference Between the Win32 and _Win32 Defines in C++
How to Provider User with Autocomplete Suggestions for Given Boost::Spirit Grammar
Is Circumventing a Class' Constructor Legal or Does It Result in Undefined Behaviour
Std::Function and Std::Bind: What Are They, and When Should They Be Used
C++11 Member Initializer List VS In-Class Initializer