Enums replace magic numbers and bool parameters; enum class does it without the name clashes and silent conversions of old enums.
An enumeration is a type with a fixed set of named values. C++ has two kinds.
The old C-style enum Color { Red, Green }; puts Red and Green directly into the surrounding scope (so two enums can't both have None) and converts silently to int, so Red + 3 and if (color == 1) compile.
The C++11 scoped enum, enum class Level { Debug, Info, Warn, Error };, fixes both problems:
- Values are scoped: you write
Level::Warn. - No implicit conversion to integers; use
static_cast<int>(l)when you need the number, or C++23'sstd::to_underlying(l). - Comparisons between values of the same enum still work:
l >= Level::Warn.
You can fix the underlying type, enum class Level : std::uint8_t, to control size in structs and binary formats; for scoped enums it defaults to int. Values count up from 0 unless you assign them explicitly (Error = 40). Enums with a specified underlying type can be forward-declared.
switch on an enum is the natural way to handle each case. If the switch has no default, -Wall warns when you forget an enumerator, which is a useful safety net when someone adds a new value later. C++20's using enum Level; brings the names into a scope so you can write case Warn: instead of case Level::Warn:.
Use enum class for states, modes and options instead of int codes or several bool parameters: open(path, Mode::ReadOnly) reads far better than open(path, true, false). There's still no built-in enum-to-string conversion; write a function with a switch.
Example
#include <cstdint>
#include <iostream>
enum Color { Red, Green }; // old style: names leak, converts to int
enum class Level : std::uint8_t { Debug, Info, Warn, Error }; // scoped, one byte
const char* label(Level l) {
using enum Level; // C++20: no Level:: prefix needed below
switch (l) { // -Wall warns if a case is missing
case Debug: return "debug";
case Info: return "info";
case Warn: return "warn";
case Error: return "error";
}
return "?";
}
int main() {
Level l = Level::Warn;
// int bad = l; // error: no implicit conversion
int n = static_cast<int>(l); // 2 (C++23: std::to_underlying(l))
bool loud = l >= Level::Warn; // same-enum comparison is fine
int c = Green; // compiles: unscoped enums convert silently
std::cout << label(l) << ' ' << n << ' ' << loud << ' ' << c << '\n'; // warn 2 1 1
}Remember
- Prefer
enum class: scoped names, no implicit int conversion - Choose the underlying type with
: std::uint8_twhen size matters - Switch without
defaultso the compiler flags missing cases
Watch out for
- Adding a
default:to every enum switch, which silences the warning that would tell you about a newly added enumerator.