[c++] Why is enum class preferred over plain enum?

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

But what does that really mean?

This question is related to c++ class enums c++-faq

The answer is


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.

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

  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

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??)

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

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;
}

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
};

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.


Examples related to c++

Method Call Chaining; returning a pointer vs a reference? How can I tell if an algorithm is efficient? Difference between opening a file in binary vs text How can compare-and-swap be used for a wait-free mutual exclusion for any shared data structure? Install Qt on Ubuntu #include errors detected in vscode Cannot open include file: 'stdio.h' - Visual Studio Community 2017 - C++ Error How to fix the error "Windows SDK version 8.1" was not found? Visual Studio 2017 errors on standard headers How do I check if a Key is pressed on C++

Examples related to class

String method cannot be found in a main class method Class constructor type in typescript? ReactJS - Call One Component Method From Another Component How do I declare a model class in my Angular 2 component using TypeScript? When to use Interface and Model in TypeScript / Angular Swift Error: Editor placeholder in source file Declaring static constants in ES6 classes? Creating a static class with no instances In R, dealing with Error: ggplot2 doesn't know how to deal with data of class numeric Static vs class functions/variables in Swift classes?

Examples related to enums

Enums in Javascript with ES6 Check if value exists in enum in TypeScript Why Python 3.6.1 throws AttributeError: module 'enum' has no attribute 'IntFlag'? TypeScript enum to object array How can I loop through enum values for display in radio buttons? How to get all values from python enum class? Get enum values as List of String in Java 8 enum to string in modern C++11 / C++14 / C++17 and future C++20 Implementing Singleton with an Enum (in Java) Swift: Convert enum value to String?

Examples related to c++-faq

What are the new features in C++17? Why should I use a pointer rather than the object itself? Why is enum class preferred over plain enum? gcc/g++: "No such file or directory" What is an undefined reference/unresolved external symbol error and how do I fix it? When is std::weak_ptr useful? What XML parser should I use in C++? What is a lambda expression in C++11? Why should C++ programmers minimize use of 'new'? Iterator invalidation rules