Enum VS Strongly Typed Enum

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:

  1. enum classes
  2. Plain enums

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 classes - enumerator names are local to the enum and their values do not implicitly convert to other types (like another enum or int)

  • Plain enums - 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 classes 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.

  1. Strongly-typed enums are not intended to be used as integral values. Thus the 'is-a' relationship indicated by inheritance does not fit.
  2. 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).
  3. 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:

  1. Provide type safety, thus eliminating implicit conversion to integer by integral promotion.
  2. Specify underlying types.
  3. 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



Leave a reply



Submit