C++ Enumerations (enum)
Introduction to Enumerations
Enumerations (enums) in C++ are user-defined types that consist of a set of named integer constants (enumerators).
They improve code readability and maintainability by replacing 'magic numbers' with meaningful names.
Basic Enum Syntax
enum EnumName {
CONSTANT1,
CONSTANT2,
// ... more constants
};
Enum Values and Underlying Type
By default (when you do not assign values explicitly):
- The first enumerator has value 0.
- Each subsequent enumerator increases by 1 from the previous enumerator’s value.
- The underlying integer type of an unscoped enum is implementation-defined but must be able to represent all enumerator values.
Enumerators may be assigned explicit values (including negative values), and multiple enumerators may share the same value.
enum Color {
RED, // 0
GREEN, // 1
BLUE // 2
};
Custom Enum Values
Enumerators can be explicitly assigned integer values:
enum HttpStatus {
OK = 200,
CREATED = 201,
BAD_REQUEST = 400,
NOT_FOUND = 404,
SERVER_ERROR = 500
};
Scoped Enums (enum class) — C++11
C++11 introduced strongly-typed, scoped enums for safer use:
enum class Month {
JAN = 1, FEB, MAR, APR, MAY, JUN,
JUL, AUG, SEP, OCT, NOV, DEC
};
Month current = Month::JAN;
Feature | Traditional enum | enum class (scoped) |
---|---|---|
Scope | Unscoped (enumerators appear in enclosing scope) | Scoped (use EnumName::Enumerator) |
Type safety | Implicit conversion to int allowed | No implicit conversion to int |
Underlying type | Implementation-defined (unless specified) | Specifiable (default: int) |
Forward declaration | Allowed only if a fixed underlying type is specified (e.g., enum E : int;) | Supported (e.g., enum class E;) |
Specifying Underlying Type
You can specify the underlying integer type of an enum:
// Traditional enum with fixed underlying type (C++11)
enum Color : unsigned char {
RED = 1, GREEN = 2, BLUE = 4
};
// Scoped enum with fixed underlying type
enum class Status : uint8_t {
OK = 0,
WARNING = 1,
ERROR = 2
};
Forward Declarations
Forward declaration rules:
- Unscoped: you may forward-declare only if you fix the underlying type (e.g., enum E : int;
).
- Scoped: you may forward-declare without specifying the underlying type (defaults to int
).
// OK (scoped enum)
enum class TokenKind;
// OK (unscoped with fixed underlying type)
enum ErrorCode : int;
// Not allowed (unscoped without fixed underlying type)
// enum Color; // ill-formed
Enum Operations
You can compare values of the same enum type. Arithmetic on scoped enums requires explicit conversion; unscoped enums permit more implicit conversions.
#include <iostream>
using namespace std;
enum class Direction { NORTH, SOUTH, EAST, WEST };
int main() {
Direction dir = Direction::NORTH;
if (dir == Direction::NORTH) {
cout << "Facing north\n";
}
// Cannot do arithmetic directly on a scoped enum value:
// dir++; // Error
// With cast:
int next = static_cast<int>(dir) + 1;
cout << "next index: " << next << "\n";
}
Bitmask Enums
Enums are commonly used for flags and bitmasks. With scoped enums, define bitwise operators to avoid repetitive casts.
#include <cstdint>
#include <type_traits>
#include <iostream>
enum class FilePermissions : uint8_t {
NONE = 0,
READ = 1u << 0,
WRITE = 1u << 1,
EXECUTE = 1u << 2
};
// Enable bitwise operators for the enum
auto operator|(FilePermissions a, FilePermissions b) -> FilePermissions {
using U = std::underlying_type_t<FilePermissions>;
return static_cast<FilePermissions>(static_cast<U>(a) | static_cast<U>(b));
}
auto operator&(FilePermissions a, FilePermissions b) -> FilePermissions {
using U = std::underlying_type_t<FilePermissions>;
return static_cast<FilePermissions>(static_cast<U>(a) & static_cast<U>(b));
}
int main() {
FilePermissions perms = FilePermissions::READ | FilePermissions::WRITE;
if ((perms & FilePermissions::READ) != FilePermissions::NONE) {
std::cout << "Read permission granted\n";
}
}
Iterating Over Enums
Enums cannot be directly iterated. One approach is to add a sentinel enumerator, but this only works if values are contiguous starting at 0.
enum class Color { RED, GREEN, BLUE, COUNT };
for (int i = 0; i < static_cast<int>(Color::COUNT); ++i) {
Color c = static_cast<Color>(i);
// use c ...
}
Real-World Example: State Machine
Enums are ideal for modeling state machines, such as traffic lights:
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
enum class TrafficLight { RED, YELLOW, GREEN };
void printLight(TrafficLight light) {
switch (light) {
case TrafficLight::RED: cout << "\xF0\x9F\x94\xB4 RED - Stop!"; break; // 🔴
case TrafficLight::YELLOW: cout << "\xF0\x9F\x9F\xA1 YELLOW - Prepare to stop"; break; // 🟡
case TrafficLight::GREEN: cout << "\xF0\x9F\x9F\xA2 GREEN - Go!"; break; // 🟢
}
cout << '\n';
}
int main() {
using namespace std::chrono_literals;
TrafficLight light = TrafficLight::RED;
for (;;) {
printLight(light);
switch (light) {
case TrafficLight::RED: this_thread::sleep_for(5s); light = TrafficLight::GREEN; break;
case TrafficLight::GREEN: this_thread::sleep_for(5s); light = TrafficLight::YELLOW; break;
case TrafficLight::YELLOW: this_thread::sleep_for(2s); light = TrafficLight::RED; break;
}
}
}
🔴 RED - Stop! 🟢 GREEN - Go! 🟡 YELLOW - Prepare to stop 🔴 RED - Stop! ...
Best Practices
- Prefer enum class
for type safety and scoping.
- Use descriptive, consistent names for enumerators.
- For flags, assign powers of two and provide bitwise operator overloads.
- Specify the underlying type when ABI size matters or for bitmasks.
- Be cautious with iteration; maintain a list/array of values or a sentinel only when appropriate.
- Avoid casting between unrelated enum types.
- From C++23, prefer std::to_underlying
for conversions.