Structs, Enums, and Type Aliases

Scoped Enumerations with enum class

Regular enumerations declare their names in the surrounding scope and automatically allow themselves to be converted to integers. In both of these cases, you can get surprising side effects. A scoped enumeration hides the names within itself and prohibits the automatic conversion to an integer type.

A scoped enumeration is declared using enum class before the type name and a list of enumerators.

#include <iostream>

enum class Color
{
    red,
    green,
    blue
};

int main()
{
    Color paint { Color::red };
    std::cout << static_cast<int>(paint) << '\n';

    return 0;
}

This declaration introduces a new type, Color, which has the enumerators red, green, and blue only within its own scope. In order to use them, you have to qualify them as in Color::red. paint is initialized with the enumerator of that name. Since a scoped enumeration does not automatically convert to an int, you must explicitly cast to an integer using a static_cast before printing; in this case, the print statement will output 0, the default int value of the first enumerator.

This cast must be explicitly present whenever you need to use the underlying int.

#include <iostream>

enum class Color
{
    red,
    green,
    blue
};

int main()
{
    Color paint { Color::green };
    // Perform this conversion explicitly
    int code { static_cast<int>(paint) };

    std::cout << code << '\n';

    return 0;
}

This initialization of code would not compile without a static_cast. With it, you store a 1 that is printed as such. The compiler forces you to acknowledge the conversion.

Because enumerators of a scoped enumeration are scoped within their own type, enumerations can reuse names without conflicting.

#include <iostream>

enum class Color
{
    red,
    green,
    blue
};

enum class TrafficLight
{
    red,
    yellow,
    green
};

int main()
{
    Color c { Color::red };
    TrafficLight t { TrafficLight::red };

    std::cout << static_cast<int>(c) << '\n';
    std::cout << static_cast<int>(t) << '\n';

    return 0;
}

You can see that there is an enumerator red in both Color and TrafficLight, but they are distinct values since the specific enumerator is part of its declaring type.

Scoped enumerations also will not permit mixing with each other or with "normal" integer types.

#include <iostream>

enum class Color
{
    red,
    green,
    blue
};

enum class Direction
{
    north,
    south
};

int main()
{
    Color paint { Color::blue };

    // Direction d { paint }; // Will not compile
    // int n { paint };       // Will not compile
    // paint = 2;             // Will not compile

    return 0;
}

Each of the lines commented out here is rejected by the compiler; a Color will not implicitly be used to initialize a Direction, it will not convert to an int without a cast, nor will it accept assignment from a normal int. This system prevents these common type related errors possible with normal enumerations.

You can still explicitly declare values in scoped enumerations:

#include <iostream>

enum class Color
{
    red = 1,
    green = 2,
    blue = 4
};

int main()
{
    Color paint { Color::blue };
    std::cout << static_cast<int>(paint) << '\n';

    return 0;
}

In this case, blue has the value 4, and paint holds that value. You can then cast to and print 4. Any unassigned enumerators follow the default sequence.

A scoped enumeration can be used in an expression where values are compared, if those values belong to the same enumeration type.

#include <iostream>

enum class Color
{
    red,
    green,
    blue
};

void printColor(Color c)
{
    if (c == Color::red)
        std::cout << 0 << '\n';
    else if (c == Color::green)
        std::cout << 1 << '\n';
    else if (c == Color::blue)
        std::cout << 2 << '\n';
}

int main()
{
    printColor(Color::green);

    return 0;
}

The parameter c receives a copy of Color::green. In both of the examples, the comparison involves two values of the same scoped type. So the comparisons are valid.

Each test prints the corresponding integer.

The reason why this readable and less error prone code also looks a little ugly is that you are forced to write Color::... each time when accessing enumerators.

Exercise: Scoped Enumerations with enum classStarter code, hints and solutionOpenClose
Guided practiceBeginner · 10–15 min

Practice: Scoped Enumerations with enum class

Work through the mission, reveal hints only when needed, and compare your result with the model solution.

Your challenge

Create Color::green and TrafficLight::green values and print both underlying numbers using explicit casts. Explain whether matching names mean matching types or values.

Mission checklist

  1. Run the starter as a baseline, then predict the change requested in Your challenge.
  2. Make that change in your editor; use the lesson-specific hints if you need help.
  3. Compile and run your answer, then compare with the worked solution and explain the result.

Starter program

This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.

#include <iostream>

enum class Color
{
    red,
    green,
    blue
};

enum class TrafficLight
{
    red,
    yellow,
    green
};

int main()
{
    Color c { Color::red };
    TrafficLight t { TrafficLight::red };

    std::cout << static_cast<int>(c) << '\n';
    std::cout << static_cast<int>(t) << '\n';

    return 0;
}

Hints

Hint 1

Color::red and TrafficLight::red belong to different enum types even though both start with underlying value zero. Qualify the enumerator with its own type.

Hint 2

Create a Color::green value and a TrafficLight::green value, then inspect their positions in the two declarations. Matching names do not require matching underlying numbers.

Worked solution

The solution creates both kinds of green. Color::green has position 1, whereas TrafficLight::green has position 2. Their matching names do not make them the same enum type or give them the same numeric value.

#include <iostream>

enum class Color
{
    red,
    green,
    blue
};

enum class TrafficLight
{
    red,
    yellow,
    green
};

int main()
{
    Color c { Color::red };
    TrafficLight t { TrafficLight::red };

    std::cout << static_cast<int>(c) << '\n';
    std::cout << static_cast<int>(t) << '\n';

    Color nextColor{Color::green};
    TrafficLight nextLight{TrafficLight::green};
    std::cout << "Green values: " << static_cast<int>(nextColor) << ' ' << static_cast<int>(nextLight) << '\n';
    return 0;
}

Example output

0
0
Green values: 1 2

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.