Why is enum class preferred over plain enum?

C++ClassEnumsC++ Faq

C++ Problem Overview


I heard a few people recommending to use enum classes in C++ because of their type safety.

But what does that really mean?

C++ Solutions


Solution 1 - C++

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.

Solution 2 - C++

From Bjarne Stroustrup's C++11 FAQ:

> The enum classes ("new enums", "strong enums") address three problems > with traditional C++ enumerations: > > * conventional enums implicitly convert to int, causing errors when someone does not want an enumeration to act as an integer. > * conventional enums export their enumerators to the surrounding scope, causing name clashes. > * the underlying type of an enum cannot be specified, causing confusion, compatibility problems, and makes forward declaration > impossible. > > The new enums are "enum class" because they combine aspects of traditional enumerations (names values) with aspects of classes (scoped members and absence of conversions).

So, as mentioned by other users, the "strong enums" would make the code safer.

The underlying type of a "classic" enum shall be an integer type large enough to fit all the values of the enum; this is usually an int. Also each enumerated type shall be compatible with char or a signed/unsigned integer type.

This is a wide description of what an enum underlying type must be, so each compiler will take decisions on his own about the underlying type of the classic enum and sometimes the result could be surprising.

For example, I've seen code like this a bunch of times:

enum E_MY_FAVOURITE_FRUITS
{
    E_APPLE      = 0x01,
    E_WATERMELON = 0x02,
    E_COCONUT    = 0x04,
    E_STRAWBERRY = 0x08,
    E_CHERRY     = 0x10,
    E_PINEAPPLE  = 0x20,
    E_BANANA     = 0x40,
    E_MANGO      = 0x80,
    E_MY_FAVOURITE_FRUITS_FORCE8 = 0xFF // 'Force' 8bits, how can you tell?
};

In the code above, some naive coder is thinking that the compiler will store the E_MY_FAVOURITE_FRUITS values into an unsigned 8bit type... but there's no warranty about it: the compiler may choose unsigned char or int or short, any of those types are large enough to fit all the values seen in the enum. Adding the field E_MY_FAVOURITE_FRUITS_FORCE8 is a burden and doesn't forces the compiler to make any kind of choice about the underlying type of the enum.

If there's some piece of code that rely on the type size and/or assumes that E_MY_FAVOURITE_FRUITS would be of some width (e.g: serialization routines) this code could behave in some weird ways depending on the compiler thoughts.

And to make matters worse, if some workmate adds carelessly a new value to our enum:

    E_DEVIL_FRUIT  = 0x100, // New fruit, with value greater than 8bits

The compiler doesn't complain about it! It just resizes the type to fit all the values of the enum (assuming that the compiler were using the smallest type possible, which is an assumption that we cannot do). This simple and careless addition to the enum could subtlety break related code.

Since C++11 is possible to specify the underlying type for enum and enum class (thanks rdb) so this issue is neatly addressed:

enum class E_MY_FAVOURITE_FRUITS : unsigned char
{
    E_APPLE        = 0x01,
    E_WATERMELON   = 0x02,
    E_COCONUT      = 0x04,
    E_STRAWBERRY   = 0x08,
    E_CHERRY       = 0x10,
    E_PINEAPPLE    = 0x20,
    E_BANANA       = 0x40,
    E_MANGO        = 0x80,
    E_DEVIL_FRUIT  = 0x100, // Warning!: constant value truncated
};

Specifying the underlying type if a field have an expression out of the range of this type the compiler will complain instead of changing the underlying type.

I think that this is a good safety improvement.

So Why is enum class preferred over plain enum?, if we can choose the underlying type for scoped(enum class) and unscoped (enum) enums what else makes enum class a better choice?:

  • They don't convert implicitly to int.
  • They don't pollute the surrounding namespace.
  • They can be forward-declared.

Solution 3 - C++

The basic advantage of using enum class over normal enums is that you may have same enum variables for 2 different enums and still can resolve them(which has been mentioned as type safe by OP)

For eg:

enum class Color1 { red, green, blue };    //this will compile
enum class Color2 { red, green, blue };

enum Color1 { red, green, blue };    //this will not compile 
enum Color2 { red, green, blue };

As for the basic enums, compiler will not be able to distinguish whether red is refering to the type Color1 or Color2 as in hte below statement.

enum Color1 { red, green, blue };   
enum Color2 { red, green, blue };
int x = red;    //Compile time error(which red are you refering to??)

Solution 4 - C++

Enumerations are used to represent a set of integer values.

The class keyword after the enum specifies that the enumeration is strongly typed and its enumerators are scoped. This way enum classes prevents accidental misuse of constants.

For Example:

enum class Animal{Dog, Cat, Tiger};
enum class Pets{Dog, Parrot};

Here we can not mix Animal and Pets values.

Animal a = Dog;       // Error: which DOG?    
Animal a = Pets::Dog  // Pets::Dog is not an Animal
 

Solution 5 - C++

It's worth noting, on top of these other answers, that C++20 solves one of the problems that enum class has: verbosity. Imagining a hypothetical enum class, Color.

void foo(Color c)
  switch (c) {
    case Color::Red: ...;
    case Color::Green: ...;
    case Color::Blue: ...;
    // etc
  }
}

This is verbose compared to the plain enum variation, where the names are in the global scope and therefore don't need to be prefixed with Color::.

However, in C++20 we can use using enum to introduce all of the names in an enum to the current scope, solving the problem.

void foo(Color c)
  using enum Color;
  switch (c) {
    case Red: ...;
    case Green: ...;
    case Blue: ...;
    // etc
  }
}

So now, there is no reason not to use enum class.

Solution 6 - C++

  1. do not implicitly convert to int
  2. can choose which type underlie
  3. ENUM namespace to avoid polluting happen
  4. Compared with normal class, can be declared forward, but do not have methods

Solution 7 - C++

C++11 FAQ mentions below points:

conventional enums implicitly convert to int, causing errors when someone does not want an enumeration to act as an integer.

enum color
{
	Red,
	Green,
	Yellow
};

enum class NewColor
{
	Red_1,
	Green_1,
	Yellow_1
};

int main()
{
	//! Implicit conversion is possible
	int i = Red;

	//! Need enum class name followed by access specifier. Ex: NewColor::Red_1
	int j = Red_1; // error C2065: 'Red_1': undeclared identifier

	//! Implicit converison is not possible. Solution Ex: int k = (int)NewColor::Red_1;
	int k = NewColor::Red_1; // error C2440: 'initializing': cannot convert from 'NewColor' to 'int'

	return 0;
}

conventional enums export their enumerators to the surrounding scope, causing name clashes.

// Header.h

enum vehicle
{
	Car,
	Bus,
	Bike,
	Autorickshow
};

enum FourWheeler
{
	Car,        // error C2365: 'Car': redefinition; previous definition was 'enumerator'
	SmallBus
};

enum class Editor
{
	vim,
	eclipes,
	VisualStudio
};

enum class CppEditor
{
	eclipes,       // No error of redefinitions
	VisualStudio,  // No error of redefinitions
	QtCreator
};

The underlying type of an enum cannot be specified, causing confusion, compatibility problems, and makes forward declaration impossible.

// Header1.h
#include <iostream>

using namespace std;

enum class Port : unsigned char; // Forward declare

class MyClass
{
public:
	void PrintPort(enum class Port p);
};

void MyClass::PrintPort(enum class Port p)
{
	cout << (int)p << endl;
}

.

// Header.h
enum class Port : unsigned char // Declare enum type explicitly
{
	PORT_1 = 0x01,
	PORT_2 = 0x02,
	PORT_3 = 0x04
};

.

// Source.cpp
#include "Header1.h"
#include "Header.h"

using namespace std;
int main()
{
	MyClass m;
	m.PrintPort(Port::PORT_1);

	return 0;
}

Solution 8 - C++

Because, as said in other answers, class enum are not implicitly convertible to int/bool, it also helps to avoid buggy code like:

enum MyEnum {
  Value1,
  Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning

Solution 9 - C++

One thing that hasn't been explicitly mentioned - the scope feature gives you an option to have the same name for an enum and class method. For instance:

class Test
{
public:
   // these call ProcessCommand() internally
   void TakeSnapshot();
   void RestoreSnapshot();
private:
   enum class Command // wouldn't be possible without 'class'
   {
        TakeSnapshot,
        RestoreSnapshot
   };
   void ProcessCommand(Command cmd); // signal the other thread or whatever
};

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionOleksiyView Question on Stackoverflow
Solution 1 - C++OleksiyView Answer on Stackoverflow
Solution 2 - C++PaperBirdMasterView Answer on Stackoverflow
Solution 3 - C++SakshamView Answer on Stackoverflow
Solution 4 - C++Alok151290View Answer on Stackoverflow
Solution 5 - C++themeemanView Answer on Stackoverflow
Solution 6 - C++Qinsheng ZhangView Answer on Stackoverflow
Solution 7 - C++SwapnilView Answer on Stackoverflow
Solution 8 - C++ArnaudView Answer on Stackoverflow
Solution 9 - C++Miro KropacekView Answer on Stackoverflow