C++ contains many useful fundamental and compound data types. But these types aren't always sufficient for the kinds of things we want to do.

For example, let's say you're writing a program that needs to keep track of a traffic light's state (red, yellow, or green), or what size a product comes in (small, medium, large). If only fundamental types were available, how might you do this?

You might store the traffic light state as an integer value, using some kind of implicit mapping (0 = red, 1 = yellow, 2 = green):

int main()
{
    int currentLight{ 0 }; // current light is red
    int productSize{ 1 };  // product size is... yellow?

    return 0;
}

But this isn't at all intuitive, and we've already discussed why magic numbers are bad. We can get rid of the magic numbers by using symbolic constants:

constexpr int red{ 0 };
constexpr int yellow{ 1 };
constexpr int green{ 2 };

int main()
{
    int currentLight{ red };
    int productSize{ yellow };

    return 0;
}

While this is a bit better for reading, the programmer is still left to deduce that currentLight and productSize (which are of type int) are meant to hold one of the values defined in the set of traffic light symbolic constants (which are likely defined elsewhere, probably in a separate file).

We can make this program a little more clear by using a type alias:

using TrafficLight = int; // define a type alias named TrafficLight

// The following light values should be used for a TrafficLight
constexpr TrafficLight red{ 0 };
constexpr TrafficLight yellow{ 1 };
constexpr TrafficLight green{ 2 };

int main()
{
    TrafficLight currentLight{ red };
    TrafficLight nextLight{ yellow };

    return 0;
}

We're getting closer. Someone reading this code still has to understand that these traffic light symbolic constants are meant to be used with variables of type TrafficLight, but at least the type has a unique name now so someone searching for TrafficLight would be able to find the set of associated symbolic constants.

However, because TrafficLight is just an alias for an int, we still have the problem that nothing enforces proper usage of these traffic light symbolic constants. We can still do something like this:

TrafficLight current{ 99 }; // syntactically valid, semantically meaningless

Also, if we debug any of these variables in our debugger, we'll only see the integer value of the light (e.g. 0), not the symbolic meaning (red), which can make it harder to tell if our program is correct.

Fortunately, we can do better.

As inspiration, consider the bool type. What makes bool particularly interesting is that it only has two defined values: true and false. We can use true or false directly (as literals), or we can instantiate a bool object and have it hold either one of those values. Additionally, the compiler is able to differentiate bool from other types. This means we can overload functions, and customize how those functions behave when passed a bool value.

If we had the ability to define our own custom types, where we could define the set of named values associated with that type, then we would have the perfect tool to elegantly solve the challenge above…

Enumerations

An enumeration (also called an enumerated type or an enum) is a compound data type whose values are restricted to a set of named symbolic constants (called enumerators).

C++ supports two kinds of enumerations: unscoped enumerations (which we'll cover now) and scoped enumerations (which we'll cover later in this chapter).

Because enumerations are program-defined types, each enumeration needs to be fully defined before we can use it (a forward declaration is not sufficient).

Unscoped enumerations

Unscoped enumerations are defined via the enum keyword.

Enumerated types are best taught by example, so let's define an unscoped enumeration that can hold some traffic light values. We'll explain how it all works below.

// Define a new unscoped enumeration named TrafficLight
enum TrafficLight
{
    // Here are the enumerators
    // These symbolic constants define all the possible values this type can hold
    // Each enumerator is separated by a comma, not a semicolon
    red,
    yellow,
    green, // trailing comma optional but recommended
}; // the enum definition must end with a semicolon

int main()
{
    // Define a few variables of enumerated type TrafficLight
    TrafficLight current{ red };      // current is red
    TrafficLight next{ yellow };      // next is yellow
    TrafficLight turnSignal{ green }; // turnSignal is green

    TrafficLight broken{ blue };     // error: blue is not an enumerator of TrafficLight
    TrafficLight flashing{ 2 };      // error: 2 is not an enumerator of TrafficLight

    return 0;
}

We start our example by using the enum keyword to tell the compiler that we are defining an unscoped enumeration, which we've named TrafficLight.

Inside a pair of curly braces, we define the enumerators for the TrafficLight type: red, yellow, and green. These enumerators define the specific values that type TrafficLight is restricted to. Each enumerator must be separated by a comma (not a semicolon)—a trailing comma after the last enumerator is optional but recommended for consistency.

It is most common to define one enumerator per line, but in simple cases (where there are a small number of enumerators and no comments are needed), they may all be defined on a single line.

The type definition for TrafficLight ends with a semicolon. We've now fully defined what enumerated type TrafficLight is!

Inside main(), we instantiate three variables of type TrafficLight: current is initialized with the light red, next is initialized with the light yellow, and turnSignal is initialized with the light green. Memory is allocated for each of these objects. Note that the initializer for an enumerated type must be one of the defined enumerators for that type. The variables broken and flashing cause compile errors because the initializers blue and 2 are not enumerators of TrafficLight.

Enumerators are implicitly constexpr.

A reminder

To quickly recap on nomenclature:

  • An enumeration or enumerated type is the program-defined type itself (e.g. TrafficLight).
  • An enumerator is a specific named value belonging to the enumeration (e.g. red).

Naming enumerations and enumerators

By convention, the names of enumerated types start with a capital letter (as do all program-defined types).

Warning
Enumerations don't have to be named, but unnamed enumerations should be avoided in modern C++.

Enumerators must be given names. Unfortunately, there is no common naming convention for enumerator names. Common choices include starting with lower case (e.g. red), starting with caps (Red), all caps (RED), all caps with a prefix (LIGHT_RED), or prefixed with a "k" and intercapped (kLightRed).

Modern C++ guidelines typically recommend avoiding the all caps naming conventions, as all caps is typically used for preprocessor macros and may conflict. We recommend also avoiding the conventions starting with a capital letter, as names beginning with a capital letter are typically reserved for program-defined types.

Best Practice
Name your enumerated types starting with a capital letter. Name your enumerators starting with a lower case letter.

Enumerated types are distinct types

Each enumerated type you create is considered to be a distinct type, meaning the compiler can distinguish it from other types (unlike typedefs or type aliases, which are considered non-distinct from the types they are aliasing).

Because enumerated types are distinct, enumerators defined as part of one enumerated type can't be used with objects of another enumerated type:

enum Direction
{
    up,
    down,
    left,
    right,
};

enum TrafficLight
{
    stop,
    caution,
    go,
};

int main()
{
    Direction heading{ stop };     // compile error: stop is not an enumerator of Direction
    TrafficLight signal{ down };   // compile error: down is not an enumerator of TrafficLight

    return 0;
}

You probably didn't want a down traffic light anyway.

Putting enumerations to use

Because enumerators are descriptive, they are useful for enhancing code documentation and readability. Enumerated types are best used when you have a smallish set of related constants, and objects only need to hold one of those values at a time.

Commonly defined enumerations include days of the week, difficulty levels, and connection states:

enum DayOfWeek
{
    sunday,
    monday,
    tuesday,
    wednesday,
    thursday,
    friday,
    saturday,
};

enum Difficulty
{
    beginner,
    intermediate,
    advanced,
    expert,
};

enum ConnectionState
{
    disconnected,
    connecting,
    connected,
    error,
};

Sometimes functions will return a status code to the caller to indicate whether the function executed successfully or encountered an error. Traditionally, small negative numbers were used to represent different possible error codes. For example:

int saveFile()
{
    if (!isOpen())
        return -1;
    if (!hasPermission())
        return -2;
    if (!hasSpace())
        return -3;

    return 0; // success
}

However, using magic numbers like this isn't very descriptive. A better method would be to use an enumerated type:

enum SaveResult
{
    success,
    fileNotOpen,
    permissionDenied,
    insufficientSpace,
};

SaveResult saveFile()
{
    if (!isOpen())
        return fileNotOpen;
    if (!hasPermission())
        return permissionDenied;
    if (!hasSpace())
        return insufficientSpace;

    return success;
}

Then the caller can test the function's return value against the appropriate enumerator, which is easier to understand than testing the return result for a specific integer value.

if (saveFile() == success)
{
    // do something
}
else
{
    // print error message
}

Enumerated types can also be put to good use in games, to identify different types of objects, or enemies, or levels. Basically, anything that is a small set of related objects.

For example:

enum PowerUp
{
    speedBoost,
    shield,
    extraLife,
};

int main()
{
    PowerUp collected{ shield };

    return 0;
}

Enumerated types can also make for useful function parameters when the user needs to make a choice between two or more options:

enum SortOrder
{
    ascending,
    descending,
    random,
};

void sortItems(SortOrder order)
{
    switch (order)
    {
        case ascending:
            // sort items low to high
            break;
        case descending:
            // sort items high to low
            break;
        case random:
            // shuffle items randomly
            break;
    }
}

Many languages use enumerations to define Booleans—after all, a Boolean is essentially just an enumeration with 2 enumerators: false and true! However, in C++, true and false are defined as keywords instead of enumerators.

Because enumerations are small and inexpensive to copy, it is fine to pass (and return) them by value.

Enums can also be used to define a collection of related bit flag positions for use with std::bitset:

#include <bitset>
#include <iostream>

namespace Flags
{
    enum Permission
    {
        read,
        write,
        execute,
        admin,
        guest,
        owner,
        shared,
        encrypted,
    };
}

int main()
{
    std::bitset<8> permissions{};
    permissions.set(Flags::read);
    permissions.set(Flags::write);

    std::cout << std::boolalpha; // print bool as true/false

    // Query a few permissions (we use the any() function to see if any bits remain set)
    std::cout << "Has read? " << permissions.test(Flags::read) << '\n';
    std::cout << "Has admin? " << permissions.test(Flags::admin) << '\n';

    return 0;
}

If you're wondering how we can use an enumerator where an integral value is expected, unscoped enumerators will implicitly convert to integral values. We will explore this further in the next lesson.

The scope of unscoped enumerations

Unscoped enumerations are named such because they put their enumerator names into the same scope as the enumeration definition itself (as opposed to creating a new scope region like a namespace does).

For example, given this program:

enum TrafficLight // this enum is defined in the global namespace
{
    red,    // so red is put into the global namespace
    yellow,
    green,
};

int main()
{
    TrafficLight current{ red }; // current is red

    return 0;
}

The TrafficLight enumeration is defined in the global scope. Therefore, all the enumeration names (red, yellow, and green) also go into the global scope. This pollutes the global scope and significantly raises the chance of naming collisions.

One consequence of this is that an enumerator name can't be used in multiple enumerations within the same scope:

enum TrafficLight
{
    stop,
    slow,
    go, // go is put into the global namespace
};

enum GameState
{
    paused,
    running,
    go, // error: naming collision with the above go
};

int main()
{
    TrafficLight light{ stop };
    GameState state{ paused };

    return 0;
}

In the above example, both unscoped enumerations (TrafficLight and GameState) put enumerators with the same name go into the global scope. This leads to a naming collision and subsequent compile error.

Unscoped enumerations also provide a named scope region for their enumerators (much like a namespace acts as a named scope region for the names declared within). This means we can access the enumerators of an unscoped enumeration as follows:

enum TrafficLight
{
    red,
    yellow,
    green, // green is put into the global namespace
};

int main()
{
    TrafficLight current{ red };               // okay, accessing enumerator from global namespace
    TrafficLight next{ TrafficLight::yellow }; // also okay, accessing enumerator from scope of TrafficLight

    return 0;
}

Most often, unscoped enumerators are accessed without using the scope resolution operator.

Avoiding enumerator naming collisions

There are quite a few common ways to prevent unscoped enumerator naming collisions.

One option is to prefix each enumerator with the name of the enumeration itself:

enum TrafficLight
{
    light_red,
    light_yellow,
    light_green,
};

enum GameState
{
    game_paused,
    game_running,
    game_go, // no longer has a naming collision with light_go
};

int main()
{
    TrafficLight signal{ light_red };
    GameState state{ game_running };

    return 0;
}

This still pollutes the namespace but reduces the chance for naming collisions by making the names longer and more unique.

A better option is to put the enumerated type inside something that provides a separate scope region, such as a namespace:

namespace Traffic
{
    // The names TrafficLight, red, yellow, and green are defined inside namespace Traffic
    enum TrafficLight
    {
        red,
        yellow,
        green,
    };
}

namespace Game
{
    enum GameState
    {
        paused,
        running,
        go, // Game::go doesn't collide with Traffic::green
    };
}

int main()
{
    Traffic::TrafficLight signal{ Traffic::red };
    Game::GameState state{ Game::paused };

    return 0;
}

This means we now have to prefix our enumeration and enumerator names with the name of the scoped region.

For Advanced Readers
Classes also provide a scope region, and it's common to put enumerated types related to a class inside the scope region of the class. We discuss this in a later lesson.

A related option is to use a scoped enumeration (which defines its own scope region). We'll discuss scoped enumerations shortly.

Best Practice
Prefer putting your enumerations inside a named scope region (such as a namespace or class) so the enumerators don't pollute the global namespace.

Alternatively, if an enumeration is only used within the body of a single function, the enumeration should be defined inside the function. This limits the scope of the enumeration and its enumerators to just that function. The enumerators of such an enumeration will shadow identically named enumerators defined in the global scope.

Comparing against enumerators

We can use the equality operators (operator== and operator!=) to test whether an enumeration has the value of a particular enumerator or not.

#include <iostream>

enum TrafficLight
{
    red,
    yellow,
    green,
};

int main()
{
    TrafficLight current{ red };

    if (current == red) // if the light is red
        std::cout << "Stop!";
    else
        std::cout << "Proceed with caution";

    return 0;
}

In the above example, we use an if-statement to test whether current is equal to the enumerator red. This gives us a way to conditionalize our program's behavior based on what enumerator our enumeration is holding.

We'll make more use of this in the next lesson.