Enumeration Types

In C++, enumerations (enum) fall into the category of constant types because they define a set of named integer constants.

enumerations (or enums) provide a way to define a set of named integer constants, improving code readability and making it easier to work with a fixed set of values. They are especially useful when representing a collection of related values that do not change, such as days of the week, error codes, or options in a menu.

Types of Enumerations in C++

There are two main types of enumerations in C++:

  1. Traditional Enumeration (Unscoped)
  2. Scoped Enumeration (C++11)

1️⃣ Traditional Enumeration (Unscoped Enum)

In traditional (unscoped) enums, the enumerator names are placed in the global scope, which means they are accessible without qualifying them with the enum name. The underlying type for these enums is usually int, and the values are implicitly assigned starting from 0 unless explicitly specified.

Syntax:

enum EnumName {
    enumerator1,  // Implicitly 0
    enumerator2,  // Implicitly 1
    enumerator3 = 10,  // Explicitly 10
    enumerator4       // Implicitly 11
};

Example:

#include <iostream>

enum Color {
    RED,     // 0
    GREEN,   // 1
    BLUE = 5,  // 5
    YELLOW   // 6 (follows BLUE)
};

int main() {
    Color color = GREEN;
    std::cout << "Color value: " << color << std::endl;  // Outputs: Color value: 1
    return 0;
}

Key Points:

  • Enumerators are automatically assigned integer values, starting from 0 by default.
  • You can assign explicit values to specific enumerators, and the rest will increment from there.
  • Enumerator names are in the global scope, so conflicts with other variables or enumerations are possible.

Limitations:

  • No strong type-checking. Enumerators are essentially integers, so they can be mixed with other integers without any compiler warnings.
  • Global scope pollution. Since enumerator names are placed in the global namespace, they can lead to name collisions.

2️⃣ Scoped Enumeration (C++11)

With the introduction of C++11, scoped enumerations (also known as enum class) were added. Scoped enums solve many of the limitations of traditional enums by keeping enumerators within the scope of the enum and providing better type safety.

Syntax:

enum class EnumName {
    enumerator1,
    enumerator2,
    enumerator3 = 10
};

Example:

#include <iostream>

enum class TrafficLight {
    RED,
    GREEN,
    YELLOW
};

int main() {
    TrafficLight light = TrafficLight::GREEN;
    if (light == TrafficLight::GREEN) {
        std::cout << "The light is green!" << std::endl;
    }
    return 0;
}

Key Points:

  • Enumerators are scoped within the enumeration, so they need to be qualified (e.g., TrafficLight::RED).
  • Type safety is enforced. You cannot implicitly convert an enum class value to an integer, which prevents accidental misuse.

Comparison of Traditional and Scoped Enums

FeatureTraditional Enum (Unscoped)Scoped Enum (enum class)
Namespace pollutionYes (global scope)No (scoped within enum)
Type safetyNoYes
Implicit conversionsYesNo
Underlying typeUsually int, but can be specifiedMust be explicitly specified or defaults to int
Enumerator namingSimple names (e.g., RED)Must use EnumName::RED

Underlying Type of Enums

In both traditional and scoped enumerations, you can specify the underlying type of the enumeration. By default, the underlying type is int, but this can be changed to any integral type (such as char, short, long, etc.).

Specifying Underlying Type in Traditional Enums

enum Color : unsigned int {
    RED = 1,
    GREEN = 2,
    BLUE = 3
};

Specifying Underlying Type in Scoped Enums

enum class Color : char {
    RED = 'r',
    GREEN = 'g',
    BLUE = 'b'
};

Operations with Enums

Conversion to Integer

Traditional enums can be implicitly converted to integers.

enum Color {
    RED, GREEN, BLUE
};

int main() {
    int colorValue = RED;  // Implicit conversion
    std::cout << "Color Value: " << colorValue << std::endl;  // Outputs: 0
    return 0;
}

For scoped enums (enum class), you need to explicitly cast them to their underlying type:

enum class Color {
    RED, GREEN, BLUE
};

int main() {
    int colorValue = static_cast<int>(Color::RED);
    std::cout << "Color Value: " << colorValue << std::endl;  // Outputs: 0
    return 0;
}

Comparing Enums

Both traditional and scoped enums can be compared directly.

if (Color::RED == Color::GREEN) {  // Always false
    // This block will not execute
}

However, scoped enums are strongly typed, meaning you cannot compare them with other enums or integers directly.

Bitwise Operations

Traditional enums allow bitwise operations (AND, OR, XOR), but scoped enums do not support bitwise operations unless you define them explicitly. For bitwise flags, traditional enums are often used with powers of two.

Bitmask Example:

enum Permission {
    READ = 1 << 0,   // 1
    WRITE = 1 << 1,  // 2
    EXECUTE = 1 << 2 // 4
};

int main() {
    int myPermissions = READ | WRITE;
    
    if (myPermissions & READ) {
        std::cout << "Read permission granted." << std::endl;
    }
    return 0;
}

For scoped enums, you'd need to define the bitwise operations manually, as they don’t support implicit bitwise operations.