Language[lang]

C++ enum and enum class: declare, convert, switch

Section
Language [lang]
Standard
C++11
Compilers
MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++)
Published
Length
7 min read
In this answer8 clauses
  1. 1What numbers are behind the names?
  2. 2enum vs enum class: what is the difference?
  3. 3How do you convert an enum to an int and back?
  4. 4How do you choose the type behind an enum?
  5. 5How do you use an enum in a switch?
  6. 6How do you convert an enum to a string?
  7. 7What does using enum do?
  8. 8An enum checklist

Abstract

A C++ enum is a type whose values are a fixed list of names, each a whole number counted from 0 unless you set one. Write enum class in new code: its names stay inside the type, as Status::Shipped, and it becomes an int only when you ask, with static_cast. Turn a value into its name with a to_string function built on a switch with no default, so GCC and Clang warn when a name has no case.

Three wordless tags hang on string loops from one rod, the middle one solid teal. Under each tag is a square cell: the first is empty, the second holds one dot, the third two dots.
Figure 1 — An enum is a fixed list of names, each a number underneath.

A C++ enum is a type you define whose values are a fixed list of names, such as Pending, Shipped and Delivered. Behind each name is a whole number, counted from 0 unless you set one. Write enum class in new code: its names stay inside the type (Status::Shipped), and it becomes an int only through a cast.

An order’s status, in a whole program:

[Example 1:
#include <iostream>

enum class Status { Pending, Shipped, Delivered };

int main() {
    Status status = Status::Shipped;

    if (status == Status::Shipped) {
        std::cout << "On its way\n";
    }
}

— end example]

It printed:

[Example 2:
On its way

— end example]

enum class arrived in C++11; Since When’s C++11 table lists the first GCC, Clang and MSVC releases with it. The samples were built with MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2 and Clang 23.1 (libc++): as C++17 on all four, and as C++11 on GCC and Clang (libc++). All four printed the same unless a sentence says otherwise. using enum needs C++20, and std::to_underlying C++23.

What numbers are behind the names?

The first name is 0 and each next name is one more than the one before, unless you give a name its own value with = ([dcl.enum]). A name after a given value counts on from it, so Created is 201:

[Example 3:
enum class Status { Pending, Shipped, Delivered };
enum class HttpCode { Ok = 200, Created, NotFound = 404 };
// ...
std::cout << static_cast<int>(Status::Pending) << ' '
          << static_cast<int>(Status::Shipped) << ' '
          << static_cast<int>(Status::Delivered) << '\n';
// 0 1 2

std::cout << static_cast<int>(HttpCode::Ok) << ' '
          << static_cast<int>(HttpCode::Created) << ' '
          << static_cast<int>(HttpCode::NotFound) << '\n';
// 200 201 404

Status s{};  // braces: the value 0, which is Pending
std::cout << std::boolalpha << (s == Status::Pending) << '\n';
// true

— end example]

All four printed the numbers in the comments. Give values when the numbers mean something outside your program, as HTTP status codes do; otherwise let the compiler count.

enum vs enum class: what is the difference?

A plain enum puts its names into the surrounding scope, the namespace or block it is declared in, and turns into an int by itself. An enum class, also called a scoped enum, keeps its names inside the type and does neither (Enumeration declaration on cppreference). With the plain kind, Blue needs no Color:: and Green lands in an int with no cast:

[Example 4:
#include <iostream>

enum Color { Red, Green, Blue };

int main() {
    int n = Green;   // no cast: n is 1
    Color c = Blue;  // no Color:: in front of Blue

    std::cout << n << '\n';  // 1
    std::cout << c << '\n';  // 2: c turns into an int here too
}

— end example]

It printed 1 and 2: even std::cout received the color as its number. Because the names are loose in the scope, two plain enums there cannot both have a Red:

[Example 5:
enum Color { Red, Green };
enum Light { Red, Amber };  // error: Red is already taken

— end example]

Clang stopped at the second one:

[Example 6:
error: redefinition of enumerator 'Red'

— end example]

An enumerator is the standard’s word for one of the names; GCC and MSVC refused the second Red too. An enum class gives each type its own names, and it will not hand its number to an int without being asked:

[Example 7:
int n = Status::Shipped;  // error: no silent conversion to int

— end example]

GCC said:

[Example 8:
error: cannot convert 'Status' to 'int' in initialization

— end example]

Top: enum Color’s Red, Green and Blue, numbered 0 to 2, stand loose in the scope; an arrow takes Green to int n. Bottom: enum class Status’s names sit in a Status:: box; int n = Status::Shipped is struck out.
Figure 2 — An enum class keeps its names inside the type and does not become an int by itself.
Question Plain enum enum class
Where the names live the surrounding scope inside the type: Status::Shipped
Turns into an int by itself only when asked: static_cast
Type behind it by default one the compiler picks to fit the values int

How do you convert an enum to an int and back?

Use static_cast<int>(status) for the number and static_cast<Status>(n) to turn a number back into a Status; a cast is a conversion you write out, and static_cast is the one for this. Since C++23, std::to_underlying from <utility> gives the number in the enum’s underlying type, the integer type behind it, without you naming that type:

[Example 9:
Status status = Status::Shipped;
int n = static_cast<int>(status);   // enum to int
std::cout << n << '\n';             // 1

int input = 2;
if (input >= 0 && input <= 2) {     // a name has this number
    Status back = static_cast<Status>(input);  // int to enum
    if (back == Status::Delivered) {
        std::cout << "Delivered\n";
    }
}

std::cout << std::to_underlying(status) << '\n';  // C++23: 1

Status odd = static_cast<Status>(7);  // compiles: no name is 7
std::cout << static_cast<int>(odd) << '\n';  // 7

— end example]

Built as C++23, all four printed 1, Delivered, 1 and 7. As C++17 the std::to_underlying line compiled on none of them, so use static_cast there. Even a plain enum needs the cast in this direction: no number turns into an enum by itself.

How do you choose the type behind an enum?

Write the type after a colon; without one, an enum class uses int:

[Example 10:
enum class Status { Pending, Shipped, Delivered };
enum class Level : std::uint8_t { Low, Medium, High };

— end example]

On these compilers sizeof(Level) printed 1 and sizeof(Status) 4, the size of an int. std::uint8_t, an 8-bit unsigned integer type from <cstdint>, is worth writing only when the size matters. It is unsigned char on these compilers, so std::cout prints a Level’s number as a character; cast it with static_cast<int> to print the number. A plain enum takes a type the same way: enum Color : int.

How do you use an enum in a switch?

Write one case per name and no default: then GCC and Clang can warn when a name has no case, which is how you find every switch to update after you add a name. This switch turns each Status into its name:

[Example 11:
const char* to_string(Status s) {
    switch (s) {
    case Status::Pending:   return "Pending";
    case Status::Shipped:   return "Shipped";
    case Status::Delivered: return "Delivered";
    }
    return "unknown";  // only a number with no name gets here
}

— end example]

It printed Pending, Shipped and Delivered for the three names. Keep the return after the switch even though every name has a case: without it, GCC and MSVC warn that the function might not return a value.

With the Delivered case left out, GCC warned with -Wall, and Clang even with no warning options: enumeration value 'Delivered' not handled in switch. MSVC printed nothing at /W4: its warning for this, C4062, is off by default, and /w14062 turns it on. A default label silences GCC’s -Wswitch, which is why this switch has none; its stricter -Wswitch-enum warns even with one.

How do you convert an enum to a string?

C++ has no function that takes an enum value and returns its name, so write one like to_string above: a switch that returns each name. C++26 adds static reflection, with which one function can name the values of every enum. std::cout << to_string(Status::Shipped) << '\n'; printed Shipped. To print a Status with << itself, overload operator<< for it and call to_string inside.

What does using enum do?

Since C++20, using enum Status; inside a function lets you write Shipped instead of Status::Shipped in that function; Since When’s C++20 table has the first releases with it:

[Example 12:
const char* to_string(Status s) {
    using enum Status;  // C++20: Pending instead of Status::Pending
    switch (s) {
    case Pending:   return "Pending";
    case Shipped:   return "Shipped";
    case Delivered: return "Delivered";
    }
    return "unknown";
}

— end example]

Built as C++20, all four printed the same three names. Outside the function, the names still need Status::.

An enum checklist

  • Write enum class, unless you maintain code built on plain enums.
  • Give a type after the colon only when the size matters.
  • Leave default out of a switch over an enum, so GCC and Clang can name a missing case.
  • Convert with static_cast, and check a number before you cast it to an enum.
  • Print the names with a to_string function built on a switch.

The Language section has the other answers on the core language.

Questions

What is the default value of an enum in C++?

An enum set with empty braces, as in Status s{};, holds the number 0, which is the first name when that name has no value of its own. A local variable declared with no braces, Status s;, is not set to anything, so give it a value before you read it.

How do you convert a string to an enum in C++?

C++ has no function for it. Write a function that compares the text with each name in turn and returns the matching value, such as Status::Shipped for “Shipped”, and decide what it returns when the text matches no name.

Can you forward declare an enum in C++?

Yes. enum class Status; declares the type with an int behind it, and a plain enum can be declared the same way only with its type given, as in enum Color : int;. Both compiled on GCC, Clang and MSVC. The names, such as Status::Shipped, can be used only after the full definition.

What is the size of an enum in C++?

The size of the integer type behind it. An enum class uses int unless you name another type after a colon: on GCC, Clang and MSVC, an enum class with no type took 4 bytes and enum class Level : std::uint8_t took 1. A plain enum with no type given uses an integer type the compiler picks to hold its values, no larger than int unless a value needs it.

How do you get the number of values in an enum?

Up to C++23 there is no built-in count. The usual trick is a last name such as Count after the real ones: when no name is given a value of its own, the number behind Count equals the number of names before it. A switch with no default then needs a case for Count too, or GCC and Clang warn that it is not handled.

Can an enum class have member functions?

No. Neither an enum class nor a plain enum can have member functions. Write ordinary functions that take one instead, such as a to_string(Status) built on a switch.

Read next

  1. C++ virtual functions: override, pure virtual and the trapsLanguage · Miriam Castellanos · written for C++11
  2. C++ array: declare, initialize and get the lengthLanguage · Miriam Castellanos · written for C++17
  3. C++ constructor: default, parameterized, copy and moveLanguage · Miriam Castellanos · written for C++11