Question

Why enum class Is Safer Than Plain enum in C++

cppenumstype-safetyenum-classc++-faq

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::Red stays inside the Color container.
  • TrafficLight::Red stays inside the TrafficLight container.
  • 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
Take Quiz

Syntax and Examples

Plain enum

enum Color { Red, Green, Blue };

Color c = Red;
int n = Red; // implicit conversion allowed

What this means

  • Red can be used without Color::
  • Red can become an int automatically
  • 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

  1. The program defines a scoped enum named Direction.
  2. Its possible values are North, South, East, and West.
  3. In main, the variable d is declared with type Direction.
  4. It is assigned the value Direction::East.
  5. The if statement compares d with Direction::East.
  6. Because both sides are the same enum type and same value, the condition is true.
  7. 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.

Take Quiz

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

Featureenumenum class
Scope of enumerator namesLeaks into surrounding scopeStays inside enum scope
Access syntaxRedColor::Red
Implicit conversion to intUsually allowedNot allowed
Type safetyWeakerStronger
Name collision riskHigherLower
Best for modern C++SometimesUsually 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.

Take Quiz

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 class keeps names scoped: Color::Red
  • enum class does not implicitly convert to int
  • plain enum exposes names like Red directly
  • plain enum is easier to misuse as an integer
  • enum class is 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 enum and 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.

Take Quiz

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 class named OrderStatus with at least three values.
  • Write a function that takes OrderStatus as a parameter and prints a matching message.
  • In main, create an order status variable and pass it to the function.
  • Use OrderStatus::ValueName syntax consistently.
  • Do not use raw integers to represent statuses.
Take Quiz

Keep learning

Related questions

View more