Question
I have heard developers recommend using enum class in C++ because it provides better type safety.
What does that mean in practice, and why is enum class considered safer than a plain enum?
For example, how do the two differ when assigning values, comparing them, or using their enumerator names in code?
Short Answer
By the end of this page, you will understand why enum class is usually safer than a plain enum in C++. You will learn how scoped enums prevent accidental conversions, avoid name collisions, and make code more explicit and reliable.
Concept
In C++, both enum and enum class are used to define a fixed set of named values.
Example:
enum Color { Red, Green, Blue };
and
enum class Color { Red, Green, Blue };
These look similar, but they behave differently.
What makes enum class safer?
enum class is safer mainly for two reasons:
- It does not implicitly convert to
int - Its enumerator names stay inside the enum’s scope
1. No accidental implicit conversion to integers
A plain enum can often be used like an integer without you noticing.
enum Color { Red, Green, Blue };
int x = Red; // Allowed
This can lead to bugs because the compiler allows mixing enum values with numbers too easily.
With enum class, that does not happen:
enum class Color { Red, Green, Blue };
int x = Color::Red; // Error
If you really want the integer value, you must convert explicitly:
int x = static_cast<int>(Color::Red);
That extra step is useful because it forces you to be intentional.
2. Enumerator names do not leak into the surrounding scope
With a plain enum, the names Red, Green, and Blue are placed directly into the surrounding scope.
enum Color { Red, Green, Blue };
enum TrafficLight { Red, Yellow, Green }; // Error: name conflict
Here, Red and Green collide.
With enum class, each name belongs to its enum type:
enum class Color { Red, Green, Blue };
enum class TrafficLight { Red, Yellow, Green };
Now you refer to them with:
Color::Red
TrafficLight::Red
This makes code clearer and avoids naming conflicts.
Why this matters in real programs
In larger codebases, safety means the compiler can catch mistakes early.
For example, imagine two different enums:
enum class FileState { Open, Closed };
enum class ConnectionState { Open, Closed };
Even though they use the same words, they represent different concepts. The compiler will not let you accidentally compare or assign one to the other.
That helps prevent logic errors and makes APIs easier to understand.
Mental Model
Think of a plain enum like a set of sticky notes with names written on them and then scattered across your desk.
- The names are easy to access.
- But they can get mixed up with other notes.
- They can also be treated like raw numbers too easily.
Think of enum class like labeled containers.
Color::Redstays inside theColorcontainer.TrafficLight::Redstays inside theTrafficLightcontainer.- You must open the right container to get the value.
That extra structure prevents mix-ups.
So the mental model is:
- plain
enum= loose named integers enum class= strongly typed, scoped named values
Syntax and Examples
Plain enum
enum Color { Red, Green, Blue };
Color c = Red;
int n = Red; // implicit conversion allowed
What this means
Redcan be used withoutColor::Redcan become anintautomatically- This is convenient, but easier to misuse
enum class
enum class Color { Red, Green, Blue };
Color c = Color::Red;
// int n = Color::Red; // error
int n = static_cast<int>(Color::Red); // explicit conversion
What this means
- You must write
Color::Red - The compiler does not silently turn it into an integer
- The code is more explicit
Safer comparisons
enum class Animal { Cat, Dog };
enum class Vehicle { Car, Bike };
Animal a = Animal::Cat;
Vehicle v = Vehicle::Car;
// if (a == v) {} // error
This is good because comparing unrelated categories usually means there is a bug.
Plain enum can be easier to mix up
enum Animal { Cat, Dog };
enum Vehicle { Car, Bike };
Animal a = Cat;
Vehicle v = Car;
// Older-style enums are much less strict and easier to misuse.
Example: status values
#include <iostream>
enum class Status { Pending, Approved, Rejected };
void printStatus(Status status) {
if (status == Status::Pending) {
std::cout << "Pending\n";
} else if (status == Status::Approved) {
std::cout << "Approved\n";
} else {
std::cout << "Rejected\n";
}
}
int main() {
Status s = Status::Approved;
printStatus(s);
}
This is clear because Status::Approved obviously belongs to Status.
Step by Step Execution
Consider this example:
#include <iostream>
enum class Direction { North, South, East, West };
int main() {
Direction d = Direction::East;
if (d == Direction::East) {
std::cout << "Going east\n";
}
}
Step by step
- The program defines a scoped enum named
Direction. - Its possible values are
North,South,East, andWest. - In
main, the variabledis declared with typeDirection. - It is assigned the value
Direction::East. - The
ifstatement comparesdwithDirection::East. - Because both sides are the same enum type and same value, the condition is true.
- The program prints:
Going east
What would fail?
These lines would not compile:
// Direction d = East; // error: must use Direction::East
// int x = Direction::East; // error: no implicit conversion to int
Those errors are exactly part of the safety benefit: the compiler blocks unclear or risky code.
Real World Use Cases
enum class is useful anywhere you want a fixed set of meaningful choices.
Common examples
- Application states
enum class AppState { Loading, Ready, Error };
- HTTP or API result categories
enum class RequestStatus { Success, Timeout, Unauthorized };
- User roles
enum class Role { Guest, Member, Admin };
- Game states
enum class GameMode { Menu, Playing, Paused, GameOver };
- Device modes
enum class PowerMode { Off, Sleep, On };
Why it helps in these cases
- The set of valid values is limited and clear.
- The names are self-documenting.
- The compiler prevents accidental mixing with integers or unrelated enums.
- Code becomes easier to read in function parameters and return values.
Example in an API
enum class RequestStatus { Success, Timeout, Unauthorized };
void handleRequest(RequestStatus status) {
if (status == RequestStatus::Timeout) {
// retry logic
}
}
This is much safer than using magic numbers like 0, 1, or 2.
Real Codebase Usage
In real projects, developers often use enum class to make APIs stricter and clearer.
1. Function parameters
enum class LogLevel { Debug, Info, Warning, Error };
void logMessage(LogLevel level, const std::string& message);
This is better than passing an int, because callers cannot accidentally pass unrelated numbers.
2. Guard clauses
void processState(Status status) {
if (status == Status::Rejected) {
return;
}
// continue processing
}
A named enum value makes the guard clause readable.
3. Validation and branching
enum class PaymentResult { Success, Failed, Pending };
bool canShip(PaymentResult result) {
return result == PaymentResult::Success;
}
4. Configuration options
enum class Theme { Light, Dark, System };
Enums are often used for configuration choices loaded from files or command-line arguments. After parsing, the rest of the program uses the enum instead of raw strings.
5. Safer refactoring
If you rename Status::Pending or add a new enum value, the compiler helps find the places that need updating.
6. Explicit conversions at boundaries
Sometimes code must talk to databases, files, or network protocols that use integers. In those places, developers convert explicitly:
int storedValue = static_cast<int>(Theme::Dark);
That keeps the unsafe part limited to the edges of the system.
Common Mistakes
1. Forgetting the scope with enum class
Broken code:
enum class Color { Red, Green, Blue };
Color c = Red; // error
Correct:
Color c = Color::Red;
2. Expecting implicit conversion to int
Broken code:
enum class Status { Ok, Error };
int code = Status::Ok; // error
Correct:
int code = static_cast<int>(Status::Ok);
Only do this when you truly need the numeric value.
3. Using plain enum and getting name collisions
Broken code:
enum Color { Red, Green };
enum TrafficLight { Red, Yellow, Green }; // conflict
Correct:
enum class Color { Red, Green };
enum class TrafficLight { Red, Yellow, Green };
4. Using integers instead of enum values
Broken code:
enum class Role { Guest, Admin };
Role r = static_cast<Role>(99); // compiles, but dangerous
Avoid creating invalid enum values from arbitrary integers unless you validate them.
5. Comparing unrelated meanings
Even if two enums have similar names, they are different types.
enum class FileState { Open, Closed };
enum class DoorState { Open, Closed };
// FileState::Open and DoorState::Open are not interchangeable.
That strictness is a feature, not a problem.
Comparisons
| Feature | enum | enum class |
|---|---|---|
| Scope of enumerator names | Leaks into surrounding scope | Stays inside enum scope |
| Access syntax | Red | Color::Red |
Implicit conversion to int | Usually allowed | Not allowed |
| Type safety | Weaker | Stronger |
| Name collision risk | Higher | Lower |
| Best for modern C++ | Sometimes | Usually yes |
enum vs enum class
Use plain enum when you specifically need older-style behavior or compatibility.
Use enum class when:
- you want safer code
- you want clearer names
- you want to avoid collisions
- you want strong typing
enum class vs constants
const int Pending = 0;
const int Approved = 1;
This is weaker than enum class because constants:
- do not form a dedicated type
- can be mixed with other integers easily
- do not clearly group related values
enum class vs strings
Strings are flexible but less safe:
std::string status = "Approved";
Problems with strings:
- typos are easy
- comparisons are slower than enum comparisons in many cases
- no compiler checking for valid values
Enums are better when the valid options are known ahead of time.
Cheat Sheet
Quick reference
Plain enum
enum Color { Red, Green, Blue };
Color c = Red;
int n = Red; // allowed
Scoped enum
enum class Color { Red, Green, Blue };
Color c = Color::Red;
int n = static_cast<int>(Color::Red);
Key rules
enum classkeeps names scoped:Color::Redenum classdoes not implicitly convert toint- plain
enumexposes names likeReddirectly - plain
enumis easier to misuse as an integer enum classis usually the better default in modern C++
When to use enum class
- fixed set of choices
- function parameters
- state values
- configuration options
- return codes with meaningful names
Watch out for
- forgetting
Type::Value - assuming automatic integer conversion
- casting random integers into enum values
- using plain
enumand causing name collisions
Useful pattern
enum class Status { Pending, Approved, Rejected };
if (status == Status::Approved) {
// do work
}
FAQ
What is the main safety benefit of enum class in C++?
The main benefit is stronger type safety. enum class prevents implicit conversion to integers and keeps enum names scoped, which reduces accidental misuse.
Why can plain enum be risky?
Plain enum values can behave like integers and their names are placed in the surrounding scope. That can cause accidental comparisons, assignments, or naming conflicts.
Do I always need to use enum class instead of enum?
Not always, but in modern C++, enum class is usually the better default unless you specifically need the older behavior of plain enums.
Why do I have to write Color::Red with enum class?
Because enum class is scoped. The value Red belongs to the type Color, so you access it as Color::Red.
How do I convert an enum class value to an integer?
Use an explicit cast:
int value = static_cast<int>(Color::Red);
This makes the conversion intentional.
Can two different enum class types use the same member names?
Yes. For example, Color::Red and TrafficLight::Red can both exist because each value stays inside its own enum scope.
Is enum class slower than plain enum?
No meaningful difference in normal use. The main difference is safety and clarity, not performance.
Mini Project
Description
Build a small C++ program that models the state of an order in an online store. This project demonstrates why enum class is useful for representing a fixed set of valid states such as Pending, Paid, and Shipped without mixing them up with integers or unrelated values.
Goal
Create a program that stores an order status, checks it safely, and prints a message based on the current state.
Requirements
- Define an
enum classnamedOrderStatuswith at least three values. - Write a function that takes
OrderStatusas a parameter and prints a matching message. - In
main, create an order status variable and pass it to the function. - Use
OrderStatus::ValueNamesyntax consistently. - Do not use raw integers to represent statuses.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.