skip to content
Mehdi Mehdikhani
Table of Contents

enum class in C++11 fixes all the problems with old C-style enums. They’re type-safe, scoped, and don’t pollute the surrounding namespace.

What enum class Actually Does

Unlike old enums, enum class creates a strongly typed enumeration:

// Old enum - problems waiting to happen
enum Color { RED, GREEN, BLUE };
enum Status { RED, ACTIVE }; // Error! RED already defined
// New enum class - properly scoped
enum class Color { Red, Green, Blue };
enum class Status { Red, Active }; // No conflict!

The values are scoped to the enum, so Color::Red and Status::Red are completely different.

Why This Is Better

Old enums had several problems:

  1. Namespace pollution: Values leaked into the surrounding scope
  2. Implicit conversions: Could accidentally convert to integers
  3. Name conflicts: Different enums couldn’t have the same value names
// Problems with old enums
enum Priority { LOW, MEDIUM, HIGH };
int x = LOW; // Implicit conversion to int
if (x == MEDIUM) { // Comparing int with enum - error prone
// ...
}

enum class fixes all of these:

enum class Priority { Low, Medium, High };
int x = Priority::Low; // Compilation error - no implicit conversion
if (x == Priority::Medium) { // Compilation error - type mismatch
// ...
}
Priority p = Priority::High; // Correct usage
if (p == Priority::Medium) { // Type-safe comparison
// ...
}

When I Actually Use enum class

Most of the time, I use enum class for:

  1. State machines: Clear, type-safe state representation:
enum class ConnectionState {
Disconnected,
Connecting,
Connected,
Reconnecting,
Failed
};
class NetworkConnection {
ConnectionState state_ = ConnectionState::Disconnected;
public:
void connect() {
if (state_ == ConnectionState::Disconnected) {
state_ = ConnectionState::Connecting;
// Start connection process
}
}
};
  1. Configuration options: Type-safe settings:
enum class LogLevel { Debug, Info, Warning, Error };
enum class CompressionType { None, Gzip, Lz4, Zstd };
class Logger {
LogLevel min_level_ = LogLevel::Info;
public:
void set_level(LogLevel level) { min_level_ = level; }
void log(LogLevel level, const std::string& message) {
if (level >= min_level_) {
// Log the message
}
}
};
  1. Error codes: Better than magic numbers:
enum class ErrorCode {
Success = 0,
FileNotFound = 1,
PermissionDenied = 2,
NetworkTimeout = 3,
InvalidFormat = 4
};
ErrorCode parse_config(const std::string& filename) {
if (!file_exists(filename)) {
return ErrorCode::FileNotFound;
}
// ...
return ErrorCode::Success;
}
  1. Options and flags: When you need distinct choices:
enum class FileMode { Read, Write, Append, ReadWrite };
enum class Alignment { Left, Center, Right, Justify };
class TextRenderer {
public:
void render(const std::string& text, Alignment align = Alignment::Left) {
switch (align) {
case Alignment::Left: render_left_aligned(text); break;
case Alignment::Center: render_centered(text); break;
case Alignment::Right: render_right_aligned(text); break;
case Alignment::Justify: render_justified(text); break;
}
}
};

Underlying Types

You can specify the underlying integer type:

enum class Status : uint8_t { Inactive = 0, Active = 1 };
enum class Priority : int { Low = -1, Normal = 0, High = 1 };

This is useful for memory optimization or when interfacing with C APIs.

Converting to/from Integers

When you need the integer value, explicit casting is required:

enum class Color { Red = 1, Green = 2, Blue = 4 };
Color c = Color::Red;
int value = static_cast<int>(c); // 1
Color from_int = static_cast<Color>(2); // Color::Green

The explicit casting makes intent clear and prevents accidents.

Switch Statements

Compilers can warn about missing cases in switch statements:

enum class Day { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday };
std::string day_name(Day d) {
switch (d) {
case Day::Monday: return "Monday";
case Day::Tuesday: return "Tuesday";
case Day::Wednesday: return "Wednesday";
case Day::Thursday: return "Thursday";
case Day::Friday: return "Friday";
// Missing Saturday and Sunday - compiler warning!
}
return "Unknown";
}

Real World Example

Here’s how I use them in a game engine:

enum class EntityType { Player, Enemy, Projectile, Pickup, Obstacle };
enum class InputAction { MoveUp, MoveDown, MoveLeft, MoveRight, Fire, Jump };
class GameEntity {
EntityType type_;
public:
GameEntity(EntityType type) : type_(type) {}
bool can_collide_with(const GameEntity& other) const {
if (type_ == EntityType::Projectile && other.type_ == EntityType::Enemy) {
return true;
}
if (type_ == EntityType::Player && other.type_ == EntityType::Pickup) {
return true;
}
return false;
}
};
class InputHandler {
std::unordered_map<InputAction, bool> pressed_keys_;
public:
bool is_pressed(InputAction action) const {
auto it = pressed_keys_.find(action);
return it != pressed_keys_.end() && it->second;
}
};

The Rule I Follow

Always use enum class instead of old-style enums. The only exception is when you need implicit conversion to integers for legacy C APIs, but even then, consider if explicit casting would be better.

enum class makes code more robust, readable, and maintainable. There’s really no reason to use old enums anymore.