logo

C++ - enum vs enum class

In C++, enumerations can be defined as either unscoped enums (enum) or scoped enums (enum class or enum struct, introduced in C++11).

In modern C++, enum class is strongly preferred for type safety and clean scoping.

Key Differences

1. Scope and Name Pollution

  • enum (Unscoped): Enumerator names leak into the enclosing scope, causing potential naming collisions.
  • enum class (Scoped): Enumerator names are scoped to the enum name and must be accessed with EnumName::Value.
// Unscoped enum: 'Red' leaks into global/enclosing scope
enum Color { Red, Green, Blue };
// enum Alert { Red, Yellow }; // Compilation Error: redefinition of 'Red'

// Scoped enum: No collisions
enum class TrafficLight { Red, Yellow, Green };
enum class ColorClass { Red, Green, Blue };

TrafficLight signal = TrafficLight::Red;
ColorClass color = ColorClass::Red;

2. Type Safety & Implicit Conversion

  • enum: Implicitly converts to integral types (int, bool, etc.), allowing accidental comparisons between unrelated enums.
  • enum class: Strongly typed. No implicit conversions to int; explicit casting (static_cast<int>(...)) is required.
enum Status { Ok, Error };
enum Priority { Low, High };

// Unscoped enum enables unintended comparisons:
if (Ok == Low) {
    // Compiles and evaluates to true because both are 0!
}

enum class StatusClass { Ok, Error };
enum class PriorityClass { Low, High };

// Scoped enum prevents accidental comparisons:
// if (StatusClass::Ok == PriorityClass::Low) {} // Compilation Error!
// int code = StatusClass::Ok;                    // Compilation Error!

int code = static_cast<int>(StatusClass::Ok);     // Explicit cast works

3. Underlying Type Specification & Forward Declaration

Both unscoped and scoped enums allow specifying underlying types (e.g. uint8_t), but scoped enums default to int and can always be forward-declared without ambiguity.

// Forward declarations:
enum class TaskState : uint8_t; // OK
enum LegacyState : uint8_t;     // OK only if underlying type is explicitly specified

// Defining underlying type:
enum class ByteFlag : uint8_t {
    Read = 1 << 0,
    Write = 1 << 1,
    Exec = 1 << 2
};

4. C++20 using enum

In C++20, you can introduce scoped enum identifiers into local scope when writing repetitive switch statements without sacrificing type safety:

void handleLight(TrafficLight light) {
    switch (light) {
        using enum TrafficLight;
        case Red:    /* ... */ break;
        case Yellow: /* ... */ break;
        case Green:  /* ... */ break;
    }
}

Comparison Summary

Feature enum (Unscoped) enum class (Scoped)
Standard C++98 / C C++11 onwards
Scope Leaks into enclosing scope Scoped to enum identifier (Type::Value)
Implicit int conversion Yes (weakly typed) No (strongly typed, requires static_cast)
Default Underlying Type Implementation-defined int
Forward Declaration Only if underlying type is given Yes, always allowed
Recommended Usage Interop with C APIs Default for modern C++