Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 2 – Types, values and expressions

C++ enums: enum class and std::to_underlying

Define scoped enumerations with enum class, convert them to and from integers safely, get -Wswitch warnings for missed cases, and build bit flags.

  • Beginner
  • 20 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free: 20 minutes

What you will learn

  • Define scoped enumerations with an explicit underlying type
  • Convert between enums and integers safely
  • Detect unhandled enumerators in a switch with compiler warnings

Before you start

What you’ll learn · Before you start

What you will learn

  • Define scoped enumerations with an explicit underlying type
  • Convert between enums and integers safely
  • Detect unhandled enumerators in a switch with compiler warnings

Before you start

On this page

An enumeration is a type whose values are a set of named constants, such as the states of a traffic light or the days of the week. Write it as an enum class (a scoped enumeration, since C++11): its names stay inside the type, as Light::red, and its values never turn into integers by accident. Each enumeration is stored as an integer type, its underlying type, which you can choose (enum class Light : std::uint8_t), and when you do need the number, std::to_underlying (C++23) gives it without spelling the type. Plain enum, the form inherited from C, still works, but its names spill into the surrounding scope and its values convert to int silently. This lesson compares the two, writes a state machine with a switch that the compiler checks for missing cases, converts in both directions safely, builds bit flags, and previews how C++26 reflection turns enumerators into names, with Apple clang 21 and GCC 16.

Plain enums and their two problems

A plain (unscoped) enum puts its enumerators into the scope around it, and they convert to int wherever an integer would fit:

unscoped.cpp C++
// Plain (unscoped) enums: their names spill into the surrounding scope,
// and they turn into int on their own.
#include <print>

enum Fruit { apple, banana, mango };
enum Colour { red, green, yellow };
// enum Signal { red, amber, green };   // would not compile: red and green are already taken

int main() {
    int total = apple + banana + mango;   // enumerators are ints in arithmetic: 0 + 1 + 2
    Fruit f = mango;
    bool same = (f == yellow);            // compares a Fruit with a Colour: both are 2
    std::println("total = {}, mango == yellow is {}", total, same);
}
Plain enums: arithmetic and a comparison across types Python · unscoped_session.py
from session import run

run("""
clang++ --version | head -n 1
clang++ -std=c++23 -Wall -Wextra unscoped.cpp -o unscoped && ./unscoped
g++-16 --version | head -n 1
g++-16 -std=c++23 -Wall -Wextra unscoped.cpp -o unscoped && ./unscoped
# C++26 removed the comparison of two different enumeration types:
clang++ -std=c++2c -Wall -Wextra unscoped.cpp -o unscoped
g++-16 -std=c++26 -Wall -Wextra unscoped.cpp -o unscoped && ./unscoped
""")

Output

$ clang++ --version | head -n 1
Apple clang version 21.0.0 (clang-2100.3.34.2)
$ clang++ -std=c++23 -Wall -Wextra unscoped.cpp -o unscoped && ./unscoped
unscoped.cpp:12:20: warning: comparison of different enumeration types ('Fruit' and 'Colour') is deprecated [-Wdeprecated-enum-compare]
   12 |     bool same = (f == yellow);            // compares a Fruit with a Colour: both are 2
      |                  ~ ^  ~~~~~~
1 warning generated.
total = 3, mango == yellow is true
$ g++-16 --version | head -n 1
g++-16 (Homebrew GCC 16.2.0) 16.2.0
$ g++-16 -std=c++23 -Wall -Wextra unscoped.cpp -o unscoped && ./unscoped
unscoped.cpp: In function 'int main()':
unscoped.cpp:12:20: warning: comparison between 'enum Fruit' and 'enum Colour' [-Wenum-compare]
   12 |     bool same = (f == yellow);            // compares a Fruit with a Colour: both are 2
      |                  ~~^~~~~~~~~
total = 3, mango == yellow is true
# C++26 removed the comparison of two different enumeration types:
$ clang++ -std=c++2c -Wall -Wextra unscoped.cpp -o unscoped
unscoped.cpp:12:20: error: invalid comparison of different enumeration types ('Fruit' and 'Colour') [-Wenum-enum-conversion]
   12 |     bool same = (f == yellow);            // compares a Fruit with a Colour: both are 2
      |                  ~ ^  ~~~~~~
1 error generated.
[exit status 1]
$ g++-16 -std=c++26 -Wall -Wextra unscoped.cpp -o unscoped && ./unscoped
unscoped.cpp: In function 'int main()':
unscoped.cpp:12:20: warning: comparison between 'enum Fruit' and 'enum Colour' [-Wenum-compare]
   12 |     bool same = (f == yellow);            // compares a Fruit with a Colour: both are 2
      |                  ~~^~~~~~~~~
total = 3, mango == yellow is true

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 unscoped_session.py

  • The names leak: red belongs to the whole file, so a second enumeration with a red, a traffic signal say, would not compile.
  • The values are integers: apple + banana + mango is 3, and a Fruit compares equal to a Colour because both are 2. C++20 deprecated comparisons and arithmetic between two different enumeration types (P1120R0), so both compilers warn, and C++26 removed them (P2864R2): Clang 21 with -std=c++2c rejects the line, while GCC 16 still only warns.

enum class: scoped and strongly typed

An enum class fixes both problems. Its enumerators are written with the type’s name, and converting one to an integer needs a cast. This traffic light is a small state machine: the state is a Light, and next() moves it along:

traffic_light.cpp C++
// A traffic light as a state machine: enum class for the states, a switch for the transitions.
#include <cstdint>
#include <print>
#include <string_view>
#include <utility>

enum class Light : std::uint8_t { red, green, yellow };

Light next(Light now) {
    switch (now) {
        case Light::red:    return Light::green;
        case Light::green:  return Light::yellow;
        case Light::yellow: return Light::red;
    }
    return Light::red;  // reached only for a value that is not one of the enumerators: fail safe
}

std::string_view name(Light light) {
    using enum Light;  // C++20: red, green and yellow can be written without Light::
    switch (light) {
        case red:    return "red";
        case green:  return "green";
        case yellow: return "yellow";
    }
    return "not a Light value";
}

int main() {
    Light light = Light::red;
    for (int step = 0; step < 4; ++step) {
        std::println("{} (stored as {})", name(light), std::to_underlying(light));
        light = next(light);
    }
    std::println("sizeof(Light) = {}", sizeof(Light));
    // int n = light;          // would not compile: no implicit conversion to int
    // if (light == 0) {}      // nor a comparison with an int
}
The traffic light, four steps Python · traffic_light_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra traffic_light.cpp -o traffic_light && ./traffic_light
g++-16 -std=c++23 -Wall -Wextra traffic_light.cpp -o traffic_light && ./traffic_light
""")

Output

$ clang++ -std=c++23 -Wall -Wextra traffic_light.cpp -o traffic_light && ./traffic_light
red (stored as 0)
green (stored as 1)
yellow (stored as 2)
red (stored as 0)
sizeof(Light) = 1
$ g++-16 -std=c++23 -Wall -Wextra traffic_light.cpp -o traffic_light && ./traffic_light
red (stored as 0)
green (stored as 1)
yellow (stored as 2)
red (stored as 0)
sizeof(Light) = 1

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 traffic_light_session.py

A cycle of three states: Light::red (0) to Light::green (1) to Light::yellow (2) and back to red, each arrow labelled next().Light::redstored as 0Light::greenstored as 1Light::yellowstored as 2next()next()next()

The traffic light of traffic_light.cpp as a state machine

Text description of the diagram

The diagram shows the three enumerators of enum class Light as boxes, each with the integer it is stored as.

  • Light::red, stored as 0, has an arrow labelled next() to Light::green.
  • Light::green, stored as 1, has an arrow labelled next() to Light::yellow.
  • Light::yellow, stored as 2, has an arrow labelled next() back to Light::red, which closes the cycle.

What the example shows:

  • The underlying type. : std::uint8_t stores each Light in one byte, which the output confirms with sizeof(Light) = 1; without it, an enum class uses int. Choose a type when the size matters (a file format, a network message, millions of values) and leave it out otherwise, as the C++ Core Guidelines advise.
  • No implicit conversion. The two commented lines at the end of main would not compile: a Light is not an int, and light == 0 compares two different kinds of thing.
  • using enum (C++20, P1099R5) brings the enumerators into a scope, so the cases in name() can say red instead of Light::red.
  • Formatting. std::println prints the result of std::to_underlying as a number even though it is a std::uint8_t; std::cout << std::uint8_t{0} would print it as a character, the one whose code is 0.

Converting between enums and integers

From an enum to its integer, use std::to_underlying (C++23, P1682R3); it is the short form of static_cast<std::underlying_type_t<E>>(e). From an integer to an enum, use static_cast, and check the value first, because the cast accepts more than the enumerators:

Conversions in both directions, and one that is undefined

conversions.cpp

// Converting between an enum and its integer, in both directions.
#include <cstdint>
#include <optional>
#include <print>
#include <type_traits>
#include <utility>

enum class Floor : std::int8_t { basement = -1, ground = 0, first = 1, second = 2 };

// Integer to enum: static_cast accepts any value of the underlying type,
// so check the range first.
std::optional<Floor> to_floor(int n) {
    if (n < -1 || n > 2) return std::nullopt;
    return static_cast<Floor>(n);
}

int main() {
    static_assert(std::is_same_v<std::underlying_type_t<Floor>, std::int8_t>);

    // Enum to integer: std::to_underlying (C++23) is the short form
    // of the static_cast below it.
    const auto a = std::to_underlying(Floor::basement);
    const auto b = static_cast<std::underlying_type_t<Floor>>(Floor::basement);
    std::println("basement = {} and {}, of type std::int8_t: {}", a, b,
                 std::is_same_v<decltype(a), const std::int8_t>);

    for (int n : {2, 5}) {
        const auto f = to_floor(n);
        std::println("to_floor({}): {}", n, f ? "a valid Floor" : "no such floor");
    }
    // allowed: 5 fits in std::int8_t, but it names no floor
    const Floor odd = static_cast<Floor>(5);
    std::println("static_cast<Floor>(5) holds {}", std::to_underlying(odd));
}

unscoped_range.cpp

// An enum without a fixed underlying type has a range set by its enumerators: here 0 to 1.
// Converting 4 into it is undefined behaviour, which a constant expression must not contain.
enum Size { small, large };

constexpr Size s = static_cast<Size>(4);

int main() { return s; }
Converting floors, and an out-of-range plain enum Python · conversions_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra conversions.cpp -o conversions && ./conversions
g++-16 -std=c++23 -Wall -Wextra conversions.cpp -o conversions && ./conversions
clang++ -std=c++23 -Wall -Wextra unscoped_range.cpp -o unscoped_range
g++-16 -std=c++23 -Wall -Wextra unscoped_range.cpp -o unscoped_range && ./unscoped_range
""")

Output

$ clang++ -std=c++23 -Wall -Wextra conversions.cpp -o conversions && ./conversions
basement = -1 and -1, of type std::int8_t: true
to_floor(2): a valid Floor
to_floor(5): no such floor
static_cast<Floor>(5) holds 5
$ g++-16 -std=c++23 -Wall -Wextra conversions.cpp -o conversions && ./conversions
basement = -1 and -1, of type std::int8_t: true
to_floor(2): a valid Floor
to_floor(5): no such floor
static_cast<Floor>(5) holds 5
$ clang++ -std=c++23 -Wall -Wextra unscoped_range.cpp -o unscoped_range
unscoped_range.cpp:5:16: error: constexpr variable 's' must be initialized by a constant expression
    5 | constexpr Size s = static_cast<Size>(4);
      |                ^   ~~~~~~~~~~~~~~~~~~~~
unscoped_range.cpp:5:20: note: integer value 4 is outside the valid range of values [0, 1] for the enumeration type 'Size'
    5 | constexpr Size s = static_cast<Size>(4);
      |                    ^
1 error generated.
[exit status 1]
$ g++-16 -std=c++23 -Wall -Wextra unscoped_range.cpp -o unscoped_range && ./unscoped_range
[exit status 4]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 conversions_session.py

For an enumeration with a fixed underlying type, every value of that type is a valid value of the enumeration (section 9.7.1 of the standard), so static_cast<Floor>(5) is allowed and holds 5, a floor that does not exist. That is why to_floor checks the range and returns std::optional<Floor>: the caller cannot forget that a number may have no floor. A plain enum without a fixed type is stricter: its values are only those that fit in the bits its enumerators need, 0 and 1 for Size, and converting anything else is undefined behaviour. In a constant expression Clang rejects static_cast<Size>(4); GCC 16 accepts it, and the program even exits with status 4.

switch over an enum, checked by the compiler

A switch over an enumeration is where enums pay off: when a new enumerator is added, the compiler can find every switch that does not handle it. -Wswitch, part of -Wall, warns when a switch without a default misses an enumerator:

missing_case.cpp C++
// A fourth state was added to Light, but one switch was not updated.
// -Wswitch (part of -Wall) notices.
#include <cstdint>
#include <string_view>

enum class Light : std::uint8_t { red, green, yellow, flashing_yellow };

std::string_view name(Light light) {
    switch (light) {
        case Light::red:    return "red";
        case Light::green:  return "green";
        case Light::yellow: return "yellow";
    }
    return "?";
}

std::string_view advice(Light light) {
    switch (light) {
        case Light::red: return "stop";
        // a default hides missing enumerators from -Wswitch
        default:         return "go carefully";
    }
}

int main() { return name(Light::flashing_yellow).size() + advice(Light::green).size(); }
A new enumerator, a forgotten case Python · missing_case_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra missing_case.cpp -o missing_case
g++-16 -std=c++23 -Wall -Wextra missing_case.cpp -o missing_case
# -Wswitch-enum also reports the switch that has a default case (only the warning lines are kept):
clang++ -std=c++23 -Wall -Wextra -Wswitch-enum missing_case.cpp -o missing_case 2>&1 | grep warning
g++-16 -std=c++23 -Wall -Wextra -Wswitch-enum missing_case.cpp -o missing_case 2>&1 | grep warning
""")

Output

$ clang++ -std=c++23 -Wall -Wextra missing_case.cpp -o missing_case
missing_case.cpp:9:13: warning: enumeration value 'flashing_yellow' not handled in switch [-Wswitch]
    9 |     switch (light) {
      |             ^~~~~
missing_case.cpp:9:13: note: add missing switch cases
    9 |     switch (light) {
      |             ^
   10 |         case Light::red:    return "red";
   11 |         case Light::green:  return "green";
   12 |         case Light::yellow: return "yellow";
   13 |     }
1 warning generated.
$ g++-16 -std=c++23 -Wall -Wextra missing_case.cpp -o missing_case
missing_case.cpp: In function 'std::string_view name(Light)':
missing_case.cpp:9:12: warning: enumeration value 'flashing_yellow' not handled in switch [-Wswitch]
    9 |     switch (light) {
      |            ^
# -Wswitch-enum also reports the switch that has a default case (only the warning lines are kept):
$ clang++ -std=c++23 -Wall -Wextra -Wswitch-enum missing_case.cpp -o missing_case 2>&1 | grep warning
missing_case.cpp:9:13: warning: enumeration value 'flashing_yellow' not handled in switch [-Wswitch]
missing_case.cpp:18:13: warning: enumeration values 'green', 'yellow', and 'flashing_yellow' not explicitly handled in switch [-Wswitch-enum]
2 warnings generated.
$ g++-16 -std=c++23 -Wall -Wextra -Wswitch-enum missing_case.cpp -o missing_case 2>&1 | grep warning
missing_case.cpp:9:12: warning: enumeration value 'flashing_yellow' not handled in switch [-Wswitch]
missing_case.cpp:18:12: warning: enumeration value 'green' not handled in switch [-Wswitch-enum]
missing_case.cpp:18:12: warning: enumeration value 'yellow' not handled in switch [-Wswitch-enum]
missing_case.cpp:18:12: warning: enumeration value 'flashing_yellow' not handled in switch [-Wswitch-enum]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 missing_case_session.py

Both compilers report flashing_yellow in name(), and say nothing about advice(), whose default covers every value. That is the cost of a default in a switch over an enum: it silences the warning for every enumerator added later. Leave the default out, handle every enumerator, and put a fallback after the switch, as next() does above. When a project wants every switch checked anyway, -Wswitch-enum warns even when a default is present, as the last two commands show.

Bit flags with enum class

An enum class can also name single bits, so that one value holds a set of flags, like the permissions of a file. Because there is no implicit conversion to an integer, you define | and & for the type once, and every use stays type-safe. This builds on the masks of the bitwise operators lesson:

permissions.cpp C++
// Bit flags with enum class: each permission is one bit,
// and two small operators combine and test them.
#include <cstdint>
#include <print>
#include <utility>

enum class Permission : std::uint8_t {
    none = 0,
    read = 1 << 0,     // 0b001
    write = 1 << 1,    // 0b010
    execute = 1 << 2,  // 0b100
};

constexpr Permission operator|(Permission a, Permission b) {
    return static_cast<Permission>(std::to_underlying(a) | std::to_underlying(b));
}
constexpr Permission operator&(Permission a, Permission b) {
    return static_cast<Permission>(std::to_underlying(a) & std::to_underlying(b));
}
constexpr bool has(Permission set, Permission p) { return (set & p) == p; }

int main() {
    constexpr Permission owner = Permission::read | Permission::write | Permission::execute;
    constexpr Permission guest = Permission::read;
    static_assert(has(owner, Permission::write) && !has(guest, Permission::write));

    for (auto [who, set] : {std::pair{"owner", owner}, std::pair{"guest", guest}}) {
        std::println("{}: {:03b}  read={} write={} execute={}", who, std::to_underlying(set),
                     has(set, Permission::read), has(set, Permission::write), has(set, Permission::execute));
    }
}
Permissions as bit flags Python · permissions_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra permissions.cpp -o permissions && ./permissions
g++-16 -std=c++23 -Wall -Wextra permissions.cpp -o permissions && ./permissions
""")

Output

$ clang++ -std=c++23 -Wall -Wextra permissions.cpp -o permissions && ./permissions
owner: 111  read=true write=true execute=true
guest: 001  read=true write=false execute=false
$ g++-16 -std=c++23 -Wall -Wextra permissions.cpp -o permissions && ./permissions
owner: 111  read=true write=true execute=true
guest: 001  read=true write=false execute=false

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 permissions_session.py

owner holds all three bits, 111, and guest only the read bit. The operators are constexpr, so even the static_assert can use them, and a Permission still cannot be mixed up with an ordinary integer.

Programmer Calculator (Bitwise) Combine flag values with OR and AND and see every bit, to check masks like these by hand.

Turning an enumerator into its name

C++23 has no way to ask an enumeration for the names of its enumerators, so code that prints them keeps a table by hand, in the same order as the enumeration, and has to be updated with it. C++26 reflection (P2996R13) lets the compiler list them. GCC 16 implements it behind -std=c++26 -freflection; Apple clang 21 does not, which the feature-test macro __cpp_impl_reflection detects:

enum_names.cpp C++
// Names of enumerators, two ways: a table written by hand (C++23),
// and reflection (C++26, today with GCC 16 only).
#include <array>
#include <print>
#include <string_view>
#include <utility>

enum class Light { red, green, yellow };

// By hand: the table must be kept in the same order as the enum.
constexpr std::array<std::string_view, 3> light_names{"red", "green", "yellow"};

constexpr std::string_view name_by_hand(Light l) { return light_names[std::to_underlying(l)]; }

#if __cpp_impl_reflection
#include <meta>

// By reflection: the compiler lists the enumerators of E, with their names.
template <typename E>
constexpr std::string_view name_by_reflection(E value) {
    template for (constexpr auto e : std::define_static_array(std::meta::enumerators_of(^^E))) {
        if (value == [:e:]) return std::meta::identifier_of(e);
    }
    return "?";
}
#endif

int main() {
    std::println("by hand: {}", name_by_hand(Light::yellow));
#if __cpp_impl_reflection
    std::println("by reflection: {}", name_by_reflection(Light::yellow));
#else
    std::println("this compiler has no reflection");
#endif
}
Enumerator names by hand and by reflection Python · enum_names_session.py
from session import run

run("""
g++-16 -std=c++26 -freflection -Wall -Wextra enum_names.cpp -o enum_names && ./enum_names
clang++ -std=c++2c -Wall -Wextra enum_names.cpp -o enum_names && ./enum_names
""")

Output

$ g++-16 -std=c++26 -freflection -Wall -Wextra enum_names.cpp -o enum_names && ./enum_names
by hand: yellow
by reflection: yellow
$ clang++ -std=c++2c -Wall -Wextra enum_names.cpp -o enum_names && ./enum_names
by hand: yellow
this compiler has no reflection

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 enum_names_session.py

^^E reflects the type, std::meta::enumerators_of lists its enumerators, template for (an expansion statement) loops over them while compiling, and std::meta::identifier_of gives each name. The track’s module on C++26 previews covers reflection in depth; until your compilers support it, keep a table, and check it with a static_assert on its size.

Exercise: weekdays as text

Write to_string, which returns the lower-case name of a Weekday, and parse_weekday, which turns such a name back into a Weekday, or into no value for anything else. The checks must all print ok:

weekday_exercise.cpp C++
// Exercise: write to_string and parse_weekday.
// Build and run this file: every check must say "ok".
//   to_string(Weekday::monday) gives "monday"; parse_weekday("monday") gives Weekday::monday;
//   parse_weekday of anything else ("Monday", "", "funday") gives no value.
#include <optional>
#include <print>
#include <string_view>

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

std::string_view to_string(Weekday day) {
    (void)day;
    return "";  // replace this
}

std::optional<Weekday> parse_weekday(std::string_view text) {
    (void)text;
    return std::nullopt;  // replace this
}

int failures = 0;

void check(bool passed, std::string_view what) {
    std::println("{}  {}", passed ? "ok    " : "FAILED", what);
    if (!passed) ++failures;
}

int main() {
    check(to_string(Weekday::monday) == "monday", "to_string(monday)");
    check(to_string(Weekday::sunday) == "sunday", "to_string(sunday)");
    check(parse_weekday("friday") == Weekday::friday, "parse_weekday(\"friday\")");
    check(!parse_weekday("Friday"), "names are lower case: \"Friday\" gives no value");
    check(!parse_weekday("") && !parse_weekday("funday"), "empty text and unknown names give no value");
    bool round_trip = true;
    for (int i = 0; i < 7; ++i) {
        const auto day = static_cast<Weekday>(i);
        round_trip = round_trip && parse_weekday(to_string(day)) == day;
    }
    check(round_trip, "parse_weekday(to_string(d)) == d for all seven days");
    std::println("{} of 6 checks failed", failures);
    return failures == 0 ? 0 : 1;
}
The exercise as given Python · weekday_exercise_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra -fsanitize=address,undefined weekday_exercise.cpp -o weekday_exercise && ./weekday_exercise
""")

Output

$ clang++ -std=c++23 -Wall -Wextra -fsanitize=address,undefined weekday_exercise.cpp -o weekday_exercise && ./weekday_exercise
FAILED  to_string(monday)
FAILED  to_string(sunday)
FAILED  parse_weekday("friday")
ok      names are lower case: "Friday" gives no value
ok      empty text and unknown names give no value
FAILED  parse_weekday(to_string(d)) == d for all seven days
4 of 6 checks failed
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 weekday_exercise_session.py

The starter returns empty results, so two checks pass by accident and four fail. First hint: keep the seven names in a constexpr std::array<std::string_view, 7> in enumeration order and index it with std::to_underlying(day); then parse_weekday can loop over the seven days and compare.

Self-check

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 What happens with these two lines?

    Read the code, then choose one answer.

    enum class Color { red, green };
    int n = Color::green;
    Show the answer to question 1

    Answer: The second line does not compile

    An enum class never converts to an integer by itself. Write int n = std::to_underlying(Color::green); (or a static_cast<int>), which gives 1.

  2. Question 2 of 5 enum class Light : std::uint8_t { red, green, yellow }; What is sizeof(Light)?

    Type a number.

    Show the answer to question 2

    Answer: 1

    An enumeration has the size of its underlying type, here one byte. Without : std::uint8_t, an enum class uses int, 4 bytes on the platforms of this track.

  3. Question 3 of 5 enum class Floor : std::int8_t { basement = -1, ground, first, second }; What does static_cast<Floor>(5) do?

    Choose one answer.

    Show the answer to question 3

    Answer: It gives a Floor that holds 5, a value that is not one of the enumerators

    With a fixed underlying type, every value of that type is a value of the enumeration, so the cast is defined. Only a plain enum without a fixed type has a narrower range, outside which the conversion is undefined.

  4. Question 4 of 5 Which of these get a warning when a new enumerator has not been handled?

    Choose every answer that is right.

    Show the answer to question 4

    Answer:

    • A switch with a default case, built with -Wswitch-enum
    • A switch without a default case, built with -Wall (which includes -Wswitch)

    -Wswitch checks switches that have no default; a default hides the gap from it. -Wswitch-enum checks every switch over an enumeration. Neither looks at if statements.

  5. Question 5 of 5 write is declared as write = 1 << 1 in enum class Permission. What is std::to_underlying(Permission::read | Permission::write), with read = 1 << 0 and the operator| of the lesson?

    Type a number.

    Show the answer to question 5

    Answer: 3

    read is 1 (binary 001) and write is 2 (binary 010); | combines them into 011, which is 3.

Interview questions

What is the difference between enum and enum class? A plain enum puts its enumerators into the enclosing scope, and its values convert to integers implicitly, so they can be added, compared with other enumerations or passed where an int is expected. An enum class keeps its enumerators inside its own scope (Color::red), never converts implicitly (static_cast or std::to_underlying is required), and has a fixed underlying type, int unless another is given. Prefer enum class; plain enums remain for old code and C interfaces.

Key takeaways

  • Prefer enum class: scoped names, no implicit conversions, int as the underlying type unless you choose one.
  • std::to_underlying (C++23) converts an enumerator to its integer; static_cast converts back, and accepts any value of the underlying type, so check the range and return std::optional from a parser.
  • Converting a value outside the range of a plain enum without a fixed type is undefined behaviour.
  • -Wswitch (in -Wall) reports enumerators missing from a switch without default; -Wswitch-enum checks the ones with default too.
  • using enum (C++20) shortens the cases of a switch.
  • Bit flags work with enum class once you define | and &.
  • Names of enumerators need a hand-written table in C++23; C++26 reflection lists them, today with GCC 16 and -freflection.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.