An Enum in C++ represents a set of named integral constants. Enums enhance readability (no more magic numbers) and make switches and state machines safer and more expressive.
1. Unscoped enum
The classic C++ enum
is unscoped: its names are introduced into the surrounding scope and implicitly convert to int
. This is convenient but can lead to name collisions and accidental integer use.
#include <iostream>
enum Color { Red, Green, Blue }; // unscoped
int main() {
Color c = Red; // implicit to int allowed
if (c == Red) std::cout << "It's red\n";
}
Output
It's red
📝 Try it Yourself: Add a
switch
onColor
and print a message for each enumerator.
1.1 Unscoped enum: Explicit start value and auto-increment
In an unscoped enum
, if the first enumerator is assigned a specific integer value, subsequent unassigned enumerators automatically increment from that value by 1.
#include <iostream>
enum Rank {
Rookie = 1, // start at 1 (not 0)
Soldier, // 2
Elite, // 3
Legend = 10,
Mythic // 11
};
int main() {
std::cout << "Rookie=" << static_cast<int>(Rookie) << "\n"
<< "Soldier=" << static_cast<int>(Soldier) << "\n"
<< "Elite=" << static_cast<int>(Elite) << "\n"
<< "Legend=" << static_cast<int>(Legend) << "\n"
<< "Mythic=" << static_cast<int>(Mythic) << "\n";
}
Output
Rookie=1
Soldier=2
Elite=3
Legend=10
Mythic=11
📝 Try it Yourself: Change
Rookie = 0
(or-5
) and verify thatSoldier
andElite
auto-increment accordingly. Then setLegend = 100
and confirmMythic
becomes 101.
1.2 Unscoped enum: Character literals as enumerator values
Enumerator values are integral constants, so using character literals is valid. By default the underlying type of an unscoped enum is int
, and character literals like 'A'
are also integral constants. Unassigned enumerators continue from the previous code point.
#include <iostream>
enum Token {
TokA = 'A', // 65 in ASCII/UTF-8
TokB, // 66 ('B')
TokZ = 'Z', // 90
TokNext // 91 ('[')
};
int main() {
std::cout << "TokA char=" << static_cast<char>(TokA)
<< " int=" << static_cast<int>(TokA) << "\n";
std::cout << "TokB char=" << static_cast<char>(TokB)
<< " int=" << static_cast<int>(TokB) << "\n";
std::cout << "TokZ char=" << static_cast<char>(TokZ)
<< " int=" << static_cast<int>(TokZ) << "\n";
std::cout << "TokNext char=" << static_cast<char>(TokNext)
<< " int=" << static_cast<int>(TokNext) << "\n";
}
Output (example)
TokA char=A int=65
TokB char=B int=66
TokZ char=Z int=90
TokNext char=[ int=91
📝 Try it Yourself: Set the first enumerator to
'x'
and verify the next unassigned enumerator becomes'y'
(one code point higher). Then set a later enumerator to'z'
and confirm the following one auto-increments to'{'
.
1.3 Unscoped enum with explicit char
underlying type
You can also specify the underlying type for an unscoped enum. With : char
, enumerators must fit within the char
range. When printing numeric codes, cast to int
for readable output.
#include <iostream>
enum Letters : char {
A = 'A', // fits in char
B, // 'B'
C // 'C'
};
int main() {
std::cout << "A as char=" << static_cast<char>(A)
<< " code=" << static_cast<int>(A) << "\n";
std::cout << "B as char=" << static_cast<char>(B)
<< " code=" << static_cast<int>(B) << "\n";
std::cout << "C as char=" << static_cast<char>(C)
<< " code=" << static_cast<int>(C) << "\n";
}
Output (example)
A as char=A code=65
B as char=B code=66
C as char=C code=67
Note: Whether char
is signed or unsigned is implementation‑defined. If you need guaranteed non‑negative codes, consider unsigned char
as the underlying type.
📝 Try it Yourself: Change the underlying type to
unsigned char
(e.g.,enum Letters : unsigned char
) and printstatic_cast<int>(...)
to verify codes remain non‑negative on your platform.
1.4 Unscoped enum: Explicit duplicates are allowed
Unscoped enums allow different enumerators to share the same value. Be cautious—this can reduce clarity in debugging and logging.
#include <iostream>
enum Suit {
Diamonds = 5,
Hearts, // 6
Clubs = 4,
Spades // 5 (same as Diamonds)
};
int main() {
std::cout << "Diamonds=" << static_cast<int>(Diamonds) << "\n"
<< "Hearts=" << static_cast<int>(Hearts) << "\n"
<< "Clubs=" << static_cast<int>(Clubs) << "\n"
<< "Spades=" << static_cast<int>(Spades) << "\n";
}
Output (example)
Diamonds=5
Hearts=6
Clubs=4
Spades=5
📝 Try it Yourself: Create an unscoped enum for weekdays where both
Sat
andSun
share the same value (e.g., 6). Print and confirm the duplicate mapping.
2. Scoped enum class (Type-Safe)
enum class
is scoped and strongly typed: no implicit conversion to int
, and names don’t pollute the surrounding scope.
This prevents accidental comparisons or assignments with unrelated enums or integers.
#include <iostream>
enum class Direction { Up, Down, Left, Right };
int main() {
Direction d = Direction::Left;
// if (d == Left) ... // ERROR: must scope with Direction::
if (d == Direction::Left) std::cout << "Go left\n";
}
Output
Go left
📝 Try it Yourself: Write a function that takes a
Direction
and returns a human-readable string (e.g., “Up”, “Down”, …). Call it for each enumerator.
3. Underlying Types and Explicit Values
You can choose the underlying integer type (e.g., std::uint8_t
) and assign explicit values.
This is useful for serialization, bit patterns, or interfacing with hardware and protocols.
#include <cstdint>
enum class Status : std::uint8_t {
Ok = 0,
Warning = 1,
Error = 2
};
📝 Try it Yourself: Create a
Permissions
unscoped enum with valuesRead = 1
,Write = 2
,Exec = 4
. Combine them using bitwise OR and print the numeric result.
4. switch with enum
switch
pairs naturally with enums. With enum class
, use fully-qualified enumerators. This improves clarity and helps the compiler catch missing cases.
#include <iostream>
enum class Mode { Read, Write, Append };
int main() {
Mode m = Mode::Write;
switch (m) {
case Mode::Read: std::cout << "Reading\n"; break;
case Mode::Write: std::cout << "Writing\n"; break;
case Mode::Append: std::cout << "Appending\n"; break;
}
}
Output
Writing
📝 Try it Yourself: Add a new
Mode::Truncate
and handle it in the switch. Then try omitting a case to see your compiler’s warnings with-Wswitch
enabled.
5. Bit Flags with Enums (Unscoped example)
Use unscoped enums for simple bit flags (or create helper operators for enum class
). You can combine flags with bitwise OR and test with bitwise AND.
#include <iostream>
enum Permission {
P_Read = 1 << 0, // 1
P_Write = 1 << 1, // 2
P_Exec = 1 << 2 // 4
};
int main() {
int userPerms = P_Read | P_Exec; // combine
if (userPerms & P_Read) std::cout << "Has read\n";
if (userPerms & P_Write) std::cout << "Has write\n";
if (userPerms & P_Exec) std::cout << "Has exec\n";
}
Output (example)
Has read
Has exec
📝 Try it Yourself: Add a
P_Admin
flag as1 << 3
, grant it to the user, and verify detection with(userPerms & P_Admin)
.
Best Practices
- Prefer
enum class
for type safety and scoping. - Specify underlying types when size matters (I/O, ABI, bit-packing).
- Use
switch
to exhaustively handle enum states (enable warnings). - For bit flags, consider unscoped enums or define bitwise operators for
enum class
.
Frequently Asked Questions (FAQ)
Q: When should I use enum class
instead of enum
?
A: Prefer enum class
for type safety and scoping. Use classic enum
only when implicit int
conversions or legacy APIs require it.
Q: Can I iterate over enum values?
A: Not directly. Create an array of enumerators, or use helper utilities. For enum class
, you typically define custom iteration support.
Q: How do I print an enum class
value?
A: Provide a mapping function (e.g., to_string(Direction)
) or overload operator<<
that converts to a string or uses static_cast<int>
when numeric output is acceptable.