Enums & enum class

C++ · Lesson 12 of 32 5 min read Updated

Many values in a program come from a small, fixed set of choices: the coach class on a train ticket, the status of an order, the day of the week, the level of a log message. You could store them as numbers (2 for AC 2-tier) or text ("2A"), but numbers are hard to read and text is easy to misspell. An enumeration (enum) lets you create your own type whose values have names.

In this lesson you will meet the old C-style enum, learn why modern C++ prefers enum class, and use enums with switch, arrays and C++20's using enum.

The problem with plain enums

C++ inherited a simple enum from C. Each name becomes a constant integer, starting from 0.

C++
#include <print>

enum Colour { red, green, blue };          // red = 0, green = 1, blue = 2
// enum Signal { red, amber, green };       // error: red and green already exist

int main() {
    Colour wall = green;
    int number = wall;                      // silently converts to int
    std::println("wall = {}", number);

    if (wall == 1) {                        // comparing with a plain number compiles
        std::println("Green, but the code does not say so");
    }
}
Output
wall = 1
Green, but the code does not say so

Two problems show up here. First, the names red, green and blue leak into the surrounding scope, so a second enum with a value called red cannot exist in the same place. Second, the values convert to int without warning, so the compiler accepts meaningless code like wall == 1 or wall + 7. These unscoped enums are still everywhere in C libraries and older C++ code.

enum class: scoped and strongly typed

C++11 added the scoped enumeration, written enum class. Its names live inside the enum, so you write Coach::ac3, and it never converts to a number by accident.

C++
#include <print>

enum class Coach { sleeper, ac3, ac2, ac1 };
enum class Signal { red, amber, green };
enum class Colour { red, green, blue };     // fine: no clash with Signal::red

int fare_per_km(Coach c) {                  // made-up rates in rupees
    switch (c) {
    case Coach::sleeper: return 1;
    case Coach::ac3:     return 2;
    case Coach::ac2:     return 3;
    case Coach::ac1:     return 5;
    }
    return 0;                               // never reached for valid values
}

int main() {
    Coach booked = Coach::ac3;
    std::println("Fare for 500 km: Rs. {}", 500 * fare_per_km(booked));

    // int n = booked;                      // error: no implicit conversion
    // if (booked == 1) {}                  // error: cannot compare with int
}
Output
Fare for 500 km: Rs. 1000

Because fare_per_km switches over every value of Coach, compiling with -Wall gives a warning if you later add a new coach class and forget to handle it here. That warning alone has saved many real programs.

Underlying types and converting to numbers

Every enum stores its values in an integer type called the underlying type. For enum class it is int unless you choose another one after a colon. You can also give values explicitly. When you really need the number, for example to save it in a file, convert on purpose:

C++
#include <cstdint>
#include <print>
#include <utility>

enum class Priority : std::uint8_t { low = 1, medium = 5, high = 10 };

int main() {
    Priority p = Priority::high;

    std::println("Stored as: {}", std::to_underlying(p));      // C++23
    std::println("Also: {}", static_cast<int>(p));
    std::println("Size: {} byte", sizeof(Priority));

    auto from_file = static_cast<Priority>(5);
    std::println("Is medium? {}", from_file == Priority::medium);
}
Output
Stored as: 10
Also: 10
Size: 1 byte
Is medium? true

std::to_underlying (header <utility>, C++23) converts an enum to its underlying type without you having to name that type. Before C++23 you wrote static_cast<int>(p). Converting the other way, from a number to an enum, also needs static_cast. Be careful: the compiler does not check that the number matches a named value, so validate numbers that come from users or files first. Choosing std::uint8_t as the underlying type makes each value take one byte, useful when you store millions of them.

Enums with switch and using enum

Enums do not know their own names, so std::println("{}", Coach::ac3) does not compile. The usual solution is a small function that turns each value into text. C++20's using enum lets you drop the Coach:: prefix inside such a function:

C++
#include <array>
#include <print>
#include <string_view>

enum class Coach { sleeper, ac3, ac2, ac1 };

std::string_view to_text(Coach c) {
    using enum Coach;                       // C++20: names usable without Coach::
    switch (c) {
    case sleeper: return "Sleeper";
    case ac3:     return "AC 3-tier";
    case ac2:     return "AC 2-tier";
    case ac1:     return "AC First Class";
    }
    return "Unknown";
}

int main() {
    constexpr std::array all_coaches = {Coach::sleeper, Coach::ac3, Coach::ac2, Coach::ac1};

    for (Coach c : all_coaches) {
        std::println("{:<15} code {}", to_text(c), static_cast<int>(c));
    }
}
Output
Sleeper         code 0
AC 3-tier       code 1
AC 2-tier       code 2
AC First Class  code 3

C++ has no built-in way to loop over all values of an enum, so the program keeps them in a constexpr std::array. The upcoming C++26 adds reflection, which can list an enum's values and names automatically; see C++26 Preview.

Common mistakes

  • Expecting enum class to print as text. It has no formatter; write a to_text function or convert to a number.
  • Using Coach values without the prefix. Outside using enum, you must write Coach::ac2, not ac2.
  • Trusting static_cast from numbers. static_cast<Coach>(42) compiles but is not a named coach. Check the range first.
  • Adding a default: to every enum switch. A default hides the helpful "enumeration value not handled" warning when you add new values. Handle every value explicitly instead.

Key points

  • An enum gives names to a fixed set of values.
  • Old unscoped enum names leak into the surrounding scope and convert to int silently.
  • enum class values are scoped (Coach::ac3) and never convert implicitly.
  • Choose the underlying type with enum class Name : std::uint8_t; convert with std::to_underlying (C++23) or static_cast.
  • using enum (C++20) removes the prefix inside a block; a switch covering every value lets the compiler warn when one is missing.

Exercise

Create enum class OrderStatus { placed, packed, shipped, delivered, cancelled }; for an online shop in Kochi. Write std::string_view to_text(OrderStatus) using using enum, and a function bool can_cancel(OrderStatus) that returns true only for placed and packed. Loop over all five statuses and print each name and whether it can be cancelled.

Show solution
C++
#include <array>
#include <print>
#include <string_view>

enum class OrderStatus { placed, packed, shipped, delivered, cancelled };

std::string_view to_text(OrderStatus s) {
    using enum OrderStatus;
    switch (s) {
    case placed:    return "Placed";
    case packed:    return "Packed";
    case shipped:   return "Shipped";
    case delivered: return "Delivered";
    case cancelled: return "Cancelled";
    }
    return "Unknown";
}

bool can_cancel(OrderStatus s) {
    return s == OrderStatus::placed || s == OrderStatus::packed;
}

int main() {
    using enum OrderStatus;
    constexpr std::array all = {placed, packed, shipped, delivered, cancelled};
    for (auto s : all) {
        std::println("{:<10} can cancel: {}", to_text(s), can_cancel(s));
    }
}
Output
Placed     can cancel: true
Packed     can cancel: true
Shipped    can cancel: false
Delivered  can cancel: false
Cancelled  can cancel: false

using enum works inside main too, which keeps the array short.

Quick Quiz

1. What is the main advantage of enum class over a plain enum?

2. How do you get the number stored in Priority::high in C++23 without naming the underlying type?

3. What does using enum Coach; do?

4. Why can a default: label in an enum switch be harmful?

Learn C++ with a mentor

Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call