Skillversitas

Enumeration and Enum Class in C++

An enumeration (enum) is a custom data type that defines a set of named values (named integral constants). Enums are very useful for representing a group of related choices, such as the days of the week, compass directions, or the status of an object. In modern C++, there are two kinds of enumeration: unscoped enums (traditional enums) and scoped enums (enum class, introduced in C++11).


Why Use Enums?

Without enums, programmers often use numbers or strings to represent choices:

// ❌ Bad: using a meaningless number
int day = 3;  // What does 3 mean? Wednesday? Thursday?

// ❌ Even worse: strings that are prone to typos
string day = "wednesday";  // Easy to mistype: "wednesay", "wednesd", etc.

With enums, the code becomes more readable and safe:

// ✅ Good: using an enum
enum class Day { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday };
Day day = Day::Wednesday;  // Clear and safe

Unscoped Enum (Traditional Enum)

An unscoped enum is a C-style enum carried over into C++. Its values are exposed directly to the scope where the enum is declared.

Code Example (Unscoped Enum)

#include <iostream>
#include <string>

using namespace std;

// Unscoped enum declaration
enum Day {
    MONDAY,    // 0 (default)
    TUESDAY,   // 1
    WEDNESDAY, // 2
    THURSDAY,  // 3
    FRIDAY,    // 4
    SATURDAY,  // 5
    SUNDAY     // 6
};

enum Season {
    Spring = 1,
    Summer = 2,
    Autumn = 3,
    Winter = 4
};

// Using a specific underlying type
enum Energy : unsigned char {
    LOW = 0,
    MEDIUM = 50,
    HIGH = 100
};

int main() {
    Day today = WEDNESDAY;  // No scope prefix needed
    Season currentSeason = Summer;
    Energy level = HIGH;

    cout << "Day index: " << today << endl;             // Output: 2
    cout << "Season: " << currentSeason << endl;        // Output: 2
    cout << "Energy level: " << (int)level << endl;     // Output: 100

    // Unscoped enums can be implicitly converted to int
    int number = TUESDAY;  // ✅ Allowed
    cout << "Number: " << number << endl;  // Output: 1

    // Comparison
    if (today == WEDNESDAY) {
        cout << "Today is Wednesday" << endl;
    }

    // Increment
    Day tomorrow = static_cast<Day>(today + 1);
    cout << "Tomorrow: " << tomorrow << endl;

    return 0;
}

Code Explanation: Unscoped Enum

Unscoped Enum Characteristics:

  • Default values start at 0 and increase sequentially
  • Values can be assigned explicitly (example: Spring = 1)
  • Implicitly converted to int — this can cause bugs
  • Enum values are exposed to the enclosing scope — prone to name conflicts
// Name conflict problem
enum Color { RED, BLUE, GREEN };
enum Fruit { RED2, MANGO, APPLE };  // Error: RED is already defined!

Scoped Enum (Enum Class, C++11)

A scoped enum (enum class) is a safer, modern version of an enumeration. Its values are limited to the enum's scope and cannot be implicitly converted to int.

Code Example (Scoped Enum)

#include <iostream>
#include <string>

using namespace std;

// Scoped enum declaration
enum class Day {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
};

enum class Color {
    Red,
    Blue,
    Green,
    Yellow,
    White,
    Black
};

enum class Direction {
    North,
    East,
    South,
    West
};

// With a specific underlying type
enum class Energy : unsigned char {
    Low = 0,
    Medium = 50,
    High = 100
};

// Enum class with a constructor
enum class MessageStatus {
    Success,
    Failed,
    Pending,
    Cancelled
};

// Function to get the status name
string statusToText(MessageStatus status) {
    switch (status) {
        case MessageStatus::Success:
            return "Success";
        case MessageStatus::Failed:
            return "Failed";
        case MessageStatus::Pending:
            return "Pending";
        case MessageStatus::Cancelled:
            return "Cancelled";
        default:
            return "Unknown";
    }
}

// Function to get the day name
string dayToText(Day day) {
    switch (day) {
        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";
        case Day::Saturday:  return "Saturday";
        case Day::Sunday:    return "Sunday";
        default:             return "Invalid";
    }
}

// Operator overloading for an enum class
Day operator+(Day day, int extra) {
    return static_cast<Day>(
        (static_cast<int>(day) + extra) % 7
    );
}

int main() {
    Day today = Day::Wednesday;     // Must use the :: scope
    Color favoriteColor = Color::Blue;
    Direction destination = Direction::North;

    cout << "=== Scoped Enum Example ===" << endl;
    cout << "Today: " << dayToText(today) << endl;
    cout << "Favorite color: ";
    switch (favoriteColor) {
        case Color::Red:    cout << "Red"; break;
        case Color::Blue:   cout << "Blue"; break;
        case Color::Green:  cout << "Green"; break;
        case Color::Yellow: cout << "Yellow"; break;
        case Color::White:  cout << "White"; break;
        case Color::Black:  cout << "Black"; break;
    }
    cout << endl;

    // Conversion to int requires static_cast
    int number = static_cast<int>(today);
    cout << "Day as number: " << number << endl;

    // Operators on an enum class
    Day tomorrow = today + 1;
    cout << "Tomorrow: " << dayToText(tomorrow) << endl;

    // Message status
    MessageStatus status = MessageStatus::Success;
    cout << "Status: " << statusToText(status) << endl;

    return 0;
}

Code Explanation: Scoped Enum

Scoped Enum (enum class) Characteristics:

  • No implicit conversion to int — you must use static_cast
  • Values are limited to the scope — they must be accessed with the enum name prefix (Day::Monday)
  • No name conflicts — two enum classes can have values with the same name
  • The underlying type can be specified — enum class MyEnum : unsigned char { ... }

Differences Between Unscoped and Scoped Enums:

Aspect Unscoped enum Scoped enum class
Value access MONDAY Day::Monday
Conversion to int Implicit (automatic) Explicit (static_cast)
Name conflicts Can occur Impossible
Type-safe No Yes
Scope Open (visible outside) Limited to the enum

Enum with an Underlying Type

Both kinds of enum allow you to specify an underlying type (base type), which determines the size and range of values.

// Unscoped enum with an underlying type
enum Energy : unsigned char {
    LOW = 0,
    MEDIUM = 50,
    HIGH = 100
};

// Scoped enum with an underlying type
enum class Level : signed char {
    Low = -128,
    Medium = 0,
    High = 127
};

// Using an underlying type for memory efficiency
enum class Status : uint8_t {   // Only 1 byte
    Active = 0,
    Inactive = 1,
    Deleted = 2
};

Enum with Methods

Although an enum does not have methods like a class, you can write functions that operate on an enum to give it additional behavior.

Approach 1: Separate Functions

#include <iostream>
#include <string>

enum class Fruit {
    Apple,
    Orange,
    Banana,
    Mango,
    Grape
};

std::string fruitName(Fruit fruit) {
    switch (fruit) {
        case Fruit::Apple:    return "Apple";
        case Fruit::Orange:   return "Orange";
        case Fruit::Banana:   return "Banana";
        case Fruit::Mango:    return "Mango";
        case Fruit::Grape:    return "Grape";
        default:              return "Unknown";
    }
}

double fruitPrice(Fruit fruit) {
    switch (fruit) {
        case Fruit::Apple:    return 15000.0;
        case Fruit::Orange:   return 12000.0;
        case Fruit::Banana:   return 8000.0;
        case Fruit::Mango:    return 20000.0;
        case Fruit::Grape:    return 35000.0;
        default:              return 0.0;
    }
}

Approach 2: Enum with a Struct Wrapper

#include <iostream>
#include <string>
#include <array>

enum class Color {
    Red,
    Blue,
    Green,
    Yellow
};

struct ColorInfo {
    std::string name;
    int hexCode;
};

ColorInfo getInfo(Color color) {
    static const std::array<ColorInfo, 4> data = {{
        {"Red",    0xFF0000},
        {"Blue",   0x0000FF},
        {"Green",  0x00FF00},
        {"Yellow", 0xFFFF00}
    }};
    return data[static_cast<size_t>(color)];
}

Everyday Use Cases

Online Store Order Status System

#include <iostream>
#include <string>

using namespace std;

enum class OrderStatus {
    Created,
    Paid,
    Processing,
    Shipped,
    Received,
    Cancelled
};

enum class PaymentMethod {
    Cash,
    Transfer,
    CreditCard,
    DebitCard,
    EWallet
};

enum class Priority {
    Low,
    Medium,
    High,
    Urgent
};

string statusText(OrderStatus status) {
    switch (status) {
        case OrderStatus::Created:    return "Order Created";
        case OrderStatus::Paid:       return "Paid";
        case OrderStatus::Processing: return "Processing";
        case OrderStatus::Shipped:    return "Shipping";
        case OrderStatus::Received:   return "Received";
        case OrderStatus::Cancelled:  return "Cancelled";
        default:                      return "Unknown Status";
    }
}

string methodText(PaymentMethod method) {
    switch (method) {
        case PaymentMethod::Cash:       return "Cash";
        case PaymentMethod::Transfer:   return "Bank Transfer";
        case PaymentMethod::CreditCard: return "Credit Card";
        case PaymentMethod::DebitCard:  return "Debit Card";
        case PaymentMethod::EWallet:    return "E-Wallet";
        default:                        return "Unknown";
    }
}

bool updateStatus(OrderStatus& status, OrderStatus targetStatus) {
    status = targetStatus;
    cout << "Status updated: " << statusText(status) << endl;
    return true;
}

int main() {
    OrderStatus order = OrderStatus::Created;
    PaymentMethod method = PaymentMethod::EWallet;

    cout << "=== Online Store Order System ===" << endl;
    cout << "Status: " << statusText(order) << endl;
    cout << "Payment method: " << methodText(method) << endl;

    // Process the order
    updateStatus(order, OrderStatus::Paid);
    updateStatus(order, OrderStatus::Processing);
    updateStatus(order, OrderStatus::Shipped);
    updateStatus(order, OrderStatus::Received);

    cout << "\nOrder complete!" << endl;

    return 0;
}

Compass Directions with Calculations

#include <iostream>
#include <string>

enum class Direction {
    North,
    Northeast,
    East,
    Southeast,
    South,
    Southwest,
    West,
    Northwest
};

Direction rotateDirection(Direction direction, int degrees) {
    int index = static_cast<int>(direction);
    index = (index + degrees / 45) % 8;
    return static_cast<Direction>(index);
}

string directionName(Direction direction) {
    switch (direction) {
        case Direction::North:     return "North";
        case Direction::Northeast: return "Northeast";
        case Direction::East:      return "East";
        case Direction::Southeast: return "Southeast";
        case Direction::South:     return "South";
        case Direction::Southwest: return "Southwest";
        case Direction::West:      return "West";
        case Direction::Northwest: return "Northwest";
        default:                   return "Unknown";
    }
}

int main() {
    Direction compass = Direction::North;
    cout << "Initial direction: " << directionName(compass) << endl;

    compass = rotateDirection(compass, 90);
    cout << "After rotating 90°: " << directionName(compass) << endl;

    compass = rotateDirection(compass, 135);
    cout << "After rotating 135°: " << directionName(compass) << endl;

    return 0;
}

Conclusion

Enumerations in C++ come in two forms: unscoped enums (traditional) and scoped enums (enum class). Unscoped enums have implicit conversion to int and values exposed to the enclosing scope, which can cause bugs. Scoped enums (enum class) are recommended because they are more type-safe, have no name conflicts, and have no implicit conversion. Use static_cast to convert an enum class to int and back. Enums are very useful for representing related choices such as statuses, directions, colors, or application modes.