Strongly Typed Enums
/ 4 min read
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 happenenum Color { RED, GREEN, BLUE };enum Status { RED, ACTIVE }; // Error! RED already defined
// New enum class - properly scopedenum 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:
- Namespace pollution: Values leaked into the surrounding scope
- Implicit conversions: Could accidentally convert to integers
- Name conflicts: Different enums couldn’t have the same value names
// Problems with old enumsenum Priority { LOW, MEDIUM, HIGH };
int x = LOW; // Implicit conversion to intif (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 conversionif (x == Priority::Medium) { // Compilation error - type mismatch // ...}
Priority p = Priority::High; // Correct usageif (p == Priority::Medium) { // Type-safe comparison // ...}When I Actually Use enum class
Most of the time, I use enum class for:
- 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 } }};- 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 } }};- 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;}- 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); // 1Color from_int = static_cast<Color>(2); // Color::GreenThe 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.