Home | Projects | Notes > C++ Programming > Enumerations
What is an enumeration?
Motivation
Structure of an enumeration
Types of enumerations
Unscoped enumeration
Scoped enumeration
Enumerations in use
A user-defined type that models a set of constant integral values.
The days of the week (Mon, Tue, Wed, ...)
The months of the year (Jan, Feb, Mar, ...)
The suits in a deck of cards (Clubs, Hearts, Spades, Diamonds)
The values in a deck of cards (Ace, Two, Three, ...)
States of a system (Idle, Defense_Mode, Attack_Mode, ...)
The directions on a compass (North, South, East, West)
Prior to enumerated types many algorithms contained unnamed numerical constants (a.k.a. "Magic numbers"). These constants are unique values with often no explanation as to how they are obtained. To make matters worse, these constants were often used as conditionals in control statements leaving one with no idea what an algorithm was doing. This often resulted in many algorithms suffering from low readability and high numbers of logic errors.
Therefore, the two important motivations for introducing the concept of enumeration were
To improve readability
To improve the algorithm correctness
Improving readability of the code
xxxxxxxxxx91int state;2std::cin >> state;3
4if (state == 0)5    initiate(3);6else if (state == 1)7    initiate(4);8else if (state == 2)9    initiate(5);Poor readability since the meaning of values used in the code is not self-explanatory.
Above code snippet can be improved by using enumerated types.
xxxxxxxxxx131enum State {Engin_Failure = 0, Inclement_Weather = 1, Nominal = 2};2enum Sequence {Abort = 3, Hold = 4, Launch = 5};3
4int user_input;5std::cin >> user_input;6State state = State(user_input);7
8if (state == Engin_Failure)             // state = 09    initiate(Abort);                    // sequence = 310else if (state == Inclement_Weather)    // state = 111    initiate(Hold);                     // sequence = 412else if (state == Nominal)              // state = 213    initiate(Launch);                   // sequence 5Improved readability!
Improving algorithm correctness
xxxxxxxxxx91int get_state()2{3    return state_of_fridge;4}5
6int getState()7{8    return state_of_rocket;9}xxxxxxxxxx81int state = get_state();2    3 if (state == 0)4    initiate(3);5else if (state == 1)6    initiate(4);7else if (state == 2)8    initiate(5);   In this scenario, even though you used the wrong state getter function, the code will still compile because both of the getter functions return
intvalues.We don't want the launch sequence to be determined by the state of the refrigerator. 
By using enumerated types, we can impose a type restriction on the state variable.
xxxxxxxxxx81State state = get_state();2    3if (state == Engin_Failure)4    initiate(Abort);5else if (state == Inclement_Weather)6    initiate(Hold);7else if (state == Nominal)8    initiate(Launch);Now, the variable
statecan only be one of the three predefined values of the enumerated typeState. If we try to assign a value to thestatevariable that is not one of these three values, the compiler will generate an error! This means that if the programmer happens to call the wrong state getter function, the program will not compile.
Syntax
xxxxxxxxxx11enum-key enum-name : enumerator-type { };
enum-key- Defines the beginning of the enumeration as well as the scope (Regardless of the type of enumeration, scoped or unscoped, an enumeration always begins with theenumkeyword.)
enum-name- Optional name of the enumeration
enumerator-type- Enumerator type; can be omitted and the compiler will try to deduce it
{ }- Enumerator list; list of enumerator definitions (names and integral values associated with them)
Enumerator initialization
Simplest form - no name, no fixed underlying type
xxxxxxxxxx21enum {Red, Green, Blue};    // Implicit initialization2//     0     1     2Unless specified otherwise, the compiler will assign 0 to the first element, and then assign the value of 'previous enumerator + 1' to all the subsequent elements. (Implicit initialization)
xxxxxxxxxx11enum {Red = 1, Green = 2, Blue = 3};    // Explicit initializationxxxxxxxxxx21enum {Red = 1, Green, Blue};    // Explicit/Implicit initialization2//               2     3Enumerator type
| Integral Type | Width in Bits | 
|---|---|
| int | at least 16 | 
| unsigned int | at least 16 | 
| long | at least 32 | 
| unsigned long | at least 32 | 
| long long | at least 64 | 
| unsigned long long | at least 64 | 
If the underlying type of enumeration is not fixed, the compiler assigns the first integral type that's able to hold the enumeration's entire value range.
xxxxxxxxxx71enum {Red, Green, Blue};                // Underlying type: int2//     0     1     23//    000   001   0104
5enum {Red, Green, Blue = -32769};       // Underlying type: long6//     0     1    -327697//    000   001   11111111111111110111111111111111In some cases, we may want to fix an enumeration's underlying type for the purpose of saving memory or increasing the accuracy of calculations involving numerator values.
xxxxxxxxxx21enum : uint8_t {Red, Green, Blue};      // Underlying type: unsigned 8 bit int2enum : long long {Red, Green, Blue};    // Underlying type: long longEnumeration name
xxxxxxxxxx71enum {Red, Green, Blue};    // Anonymous; No type safety (Not possible to declare variables2                            // of that enumeration type.)3
4int my_color;5
6my_color = Green;   // Valid7my_color = 4;       // Also validNo type safety for anonymous enumeration!
xxxxxxxxxx71enum Color {Red, Green, Blue};  // Named; Type safe2
3Color my_color;     // It is assumed that 'my_color' can only have one of the three values;4                    // Red, Green or Blue.5
6my_color = Green;   // Valid7my_color = 4;       // Not validWith the enumeration name
Color, it is now type safe!
Enumerations whose enumerators are NOT qualified and are visible throughout the entire scope in which they declared.
Syntax
xxxxxxxxxx11enum enum-name : enumerator-type { };
enum- Enum; Defines an unscoped enumeration
Using if and switch statements with unscoped enumerations
xxxxxxxxxx81State state = get_state();2    3if (state == Nominal)                   // Accessed first4    initiate(Launch);5else if (state == Inclement_Weather)    // Accessed second6    initiate(Hold);7else if (state == Engine_Failure)       // Accessed last8    initiate(Abort);This control structure may not be the best implementation. Consider a situation where the current state is
Engine_Failureand the system needs to initiateAbortsequence ASAP. With this control structure, you'll need to go through each and every conditional until it finally reachesEngine_Failure.
switch statements are by far the most popular control structure used with enumerations. (Better readability and access time than if...else statements in general.)
xxxxxxxxxx141State state = get_state();2    3switch (state)4{5    case Engine_Failure:    // Equal access time6        initiate(Abort);7        break;8    case Inclement_Weather: // Equal access time9        initiate(Hold);10        break;11    case Nominal:           // Equal access time12        initiate(Launch);13        break;14}Note that each label within a
switchstatement must be unique!
Using cin with unscoped enumerations
xxxxxxxxxx61enum State {Engine_Failure, Inclement_Weather, Nominal};2
3State state;4std::cin >> state;  // NOT allowed using standard extraction operator (This is because the5                    // extraction operator `>>` has no knowledge of what a `State` type 6                    // variable is or how to deal with it.One solution is to extract the user input value into a type that the extraction operator >> does know about, and then cast it to the State type variable.
xxxxxxxxxx51enum State {Engine_Failure, Inclement_Weather, Nominal};    // Underlying type: int2                 0                   1           23std::underlying_type_t<State> user_input;                   // Type: int4std::cin >> user_input;                                     // User input = 35State state = State(user_input);                            // state = 3When we declare the type of temporary variable, in some cases, the underlying type of an enumeration might not be obvious. So we can use the class template
std::underlying_type_tto deduce it. In this case, the underlying type of theStateenumeration is integer, so the type of theuser_inputvariable will also be integer.It's important to note that when we cast a variable to an enumeration types, the value of the variable does not change. This means that there's no guarantee that the casted enumeration type variable will have the value corresponding to the enumeration's enumerators. For example, if the user were to enter the value 3, it would be cast to the
Statetype variablestatewith no issues. Obviously, this is not what we want since 3 does not correspond to anyStateenumerator.One way to workaround this situation is to implement our own checks to ensure that the variable we are casting has a value corresponding to one of the enumeration's enumerators. This is usually done by using the
switchstatement with the condition being the variable to be casted (e.g.,user_inputin this case), and the cases being each of the enumeration's enumerator.There can be many other ways to do the checks, but the key is that it is your responsibility to perform the checks!
xxxxxxxxxx121enum State {Engine_Failure, Inclement_Weather, Nominal};    // Underlying type: int2                 0                   1           23std::underlying_type_t<State> user_input;                   // Type: int4std::cin >> user_input;                                     // User input = 35State state;6switch (user_input)7{8    case Engine_Failure:        state = State(user_input); break;9    case Inclement_Weather:     state = State(user_input); break;10    case Nominal:               state = State(user_input); break;11    default:                    std::cout << "User input is not a valid state.";12}Another solution is to overload the extraction operator >> to deal with the State type variables.
xxxxxxxxxx151enum State {Engine_Failure, Inclement_Weather, Nominal};    // Underlying type: int2                 0                   1           23std::istream& operator>>(std::istream& is, State& state)4{5    std::underlying_typt_t<State> user_input;       // Type: int6    is >> user_input;7    switch (user_input)8    {9        case Engine_Failure:        state = State(user_input); break;10        case Inclement_Weather:     state = State(user_input); break;11        case Nominal:               state = State(user_input); break;12        default:                    std::cout << "User input is not a valid state.";13    }14    return is;  // Input stream is returned to the calling function15}With the overloaded extraction operator, the following code becomes not only valid, but also more clean and readable.
xxxxxxxxxx21State state;2std::cin >> state;      // Valid with overloaded extraction operatorUsing cout with unscoped enumerations to display enumeration-type variables
xxxxxxxxxx31enum State {Engine_Failure, Inclement_Weather, Nominal};2State state = Engine_Failure;3std::cout << state;     // Displays 0In this case, the enumeration-type variable is implicitly converted to its underlying type, and its value is displayed. (Note that it's their value that gets displayed, not the enumerator's name! This makes sense, because an enumerator's name isn't a string, but is simply a name used to identify its value within the code.)
Sometimes, it may be useful to display the name of the enumeration type variable. To accomplish this, we can use switch statement.
xxxxxxxxxx91enum State {Engine_Failure, Inclement_Weather, Nominal};2State state = Engine_Failure;3switch (state)4{5    case Engine_Failure:        std::cout << "Engine Failure"; break;6    case Inclement_Weather:     std::cout << "Inclement Weather"; break;7    case Nominal:               std::cout << "Nominal"; break;8    default:                    std::cout << "Unknown";9}   // Displays "Engine Failure"Another way to accomplish this is to overload the insertion operator <<.
xxxxxxxxxx131enum State {Engine_Failure, Inclement_Weather, Nominal};2
3std::ostream& operator<<(std::ostream& os, const State& state)4{5    switch (state)6    {7        case Engine_Failure:        os << "Engine Failure";8        case Inclement_Weather:     os << "Inclement Weather";9        case Nominal:               os << "Nominal";10        default:                    os << "Unknown";11    }12    return os;  // Output stream is returned to the calling function13}L3: Note that the second parameter type must be the
constreference!
By overloading the insertion operator, we can write a clean and readable code as follows.
xxxxxxxxxx21State state = Engine_Failure;2std::cout << state;     // Displays "Engine Failure"
Enumerations whose enumerators are qualified, and therefore they are only visible by using the scope resolution operator :: . (Everything you can do with unscoped enumerations, you can do with scoped enumerations.)
Syntax
xxxxxxxxxx11enum class enum-name : enumerator-type { };
enum class(orenum struct) - Defines a scoped enumeration
Unscoped enumerations can sometimes cause issues that can only be resolved by using scoped enumerations.
Example 1:
xxxxxxxxxx51enum Whale {Blue, Beluga, Gray};2enum Shark {Greatwhite, Gammerhead, Bull};3
4if (Beluga == Hammerhead)5    std::cout << "A beluga whale is equivalent to a hammerhead shark.";Saying that
Belugawhale andHammerheadshark are equivalent just because they represent the same value 1 does not make sense!
Example 2: Name clashes!
xxxxxxxxxx21enum Whale {Blue, Beluga, Gray};2enum Shark {Greatwhite, Gammerhead, Bull, Blue};    // Error: Blue already definedWe don't want to have to worry about name clashes when we are naming enumerators.
Scoped enumerations solve these issues!
Using if and switch statements with scoped enumerations (Identical to that of unscoped, but with the additional requirement of specifying enumerator scopes)
xxxxxxxxxx91enum class Whale {Blue, Beluga, Gray};2Whale whale = Whale::Beluga;3
4if (whale == Whale::Blue)5    std::cout << "Blue whale";6else if (whale == Whale::Beluga)7    std::cout << "Beluga whale";8else if (whale == Whale::Gray)9    std::cout << "Gray whale";xxxxxxxxxx151enum class Whale {Blue, Beluga, Gray};2Whale whale = Whale::Beluga;3
4switch (whale)5{6    case Whale::Blue:7        std::cout << "Blue whale";8        break;9    case Whale::Beluga:10        std::cout << "Beluga whale";11        break;12    case Whale::Gray:13        std::cout << "Gray whale";14        break;15}Using scoped enumerator values
Unlike the unscoped enumerations, compiler will not deduce and convert the enumerator values of scoped enumerations. (Scoped enumeration type variables are not implicitly convertable! Instead, we must explicitly cast the variable to its underlying type.)
xxxxxxxxxx51enum class Item {Milk = 350, Bread = 250, Apple = 132};     // Underlying type: int2
3int milk_code = Item::Milk;             // Error: Cannot conver Item to int4int total = Item::Milk + Item::Bread;   // Error: No matching '+' operator5std::cout << Item::Milk;                // Error: No matching '<<' operatorxxxxxxxxxx81enum class Item {Milk = 350, Bread = 250, Apple = 132};     // Underlying type: int2
3int milk_code = int(Item::Milk);                    // milk_code = 3504// Or5int milk_code = static_cast<int>(Item::Milk);6
7int total = int(Item::Milk) + int(Item::Bread);     // total = 6008std::cout << underlying_type_t<Item>(Item::Milk);   // Displays 350L3, L5: Only after appropriate type casting (there can be a few different styles of casting), can we use the value of scoped enumeration type variable.