Coming Soon

This lesson is currently being developed

Scoped enumerations (enum classes)

Use modern enum classes for type safety.

Compound Types: Enums and Structs
Chapter
Beginner
Difficulty
40min
Estimated Time

What to Expect

Comprehensive explanations with practical examples

Interactive coding exercises to practice concepts

Knowledge quiz to test your understanding

Step-by-step guidance for beginners

Development Status

In Progress

Content is being carefully crafted to provide the best learning experience

Preview

Early Preview Content

This content is still being developed and may change before publication.

13.6 — Scoped enumerations (enum classes)

In this lesson, you'll learn about scoped enumerations (enum classes) - a modern, safer alternative to traditional unscoped enumerations.

Problems with unscoped enumerations

While unscoped enumerations are useful, they have several issues that can cause problems in larger programs:

1. Name pollution

Enumerators are placed in the surrounding scope, which can cause naming conflicts:

enum Color
{
    red,
    green,
    blue
};

enum TrafficLight
{
    red,    // ERROR: 'red' already defined!
    yellow,
    green   // ERROR: 'green' already defined!
};

2. Implicit conversion to integers

Unscoped enumerations implicitly convert to integers, which can lead to unexpected behavior:

#include <iostream>

enum Color { red, green, blue };
enum Size { small, medium, large };

int main()
{
    Color color = red;
    Size size = small;
    
    // This compiles but doesn't make logical sense!
    if (color == size)  // Comparing color to size?
    {
        std::cout << "This shouldn't be possible!" << std::endl;
    }
    
    // Unintended arithmetic
    int result = color + size;  // Adding color to size?
    std::cout << "Color + Size = " << result << std::endl;
    
    return 0;
}

Output:

This shouldn't be possible!
Color + Size = 0

Introduction to scoped enumerations (enum class)

Scoped enumerations (also called enum classes) solve these problems by:

  • Placing enumerators in their own scope
  • Not implicitly converting to integers
  • Providing better type safety

Here's the basic syntax:

enum class enumeration_name
{
    enumerator1,
    enumerator2,
    // ...
};

Let's see the difference:

#include <iostream>

enum class Color
{
    red,
    green,
    blue
};

enum class TrafficLight
{
    red,    // OK: no conflict with Color::red
    yellow,
    green   // OK: no conflict with Color::green
};

int main()
{
    Color favoriteColor = Color::red;  // Must use scope resolution
    TrafficLight signal = TrafficLight::red;  // Different from Color::red
    
    std::cout << "This compiles without conflicts!" << std::endl;
    
    return 0;
}

Output:

This compiles without conflicts!

Accessing scoped enumerators

With scoped enumerations, you must use the scope resolution operator (::) to access enumerators:

#include <iostream>

enum class Direction
{
    north,
    south,
    east,
    west
};

int main()
{
    Direction playerDirection = Direction::north;
    
    // This won't work:
    // Direction badDirection = north;  // ERROR: 'north' not found
    
    if (playerDirection == Direction::north)
    {
        std::cout << "Player is facing north" << std::endl;
    }
    
    return 0;
}

Output:

Player is facing north

No implicit conversion to integers

Scoped enumerations don't implicitly convert to integers, providing better type safety:

#include <iostream>

enum class Priority
{
    low,
    medium,
    high
};

int main()
{
    Priority taskPriority = Priority::high;
    
    // These won't compile:
    // int value = taskPriority;              // ERROR: no implicit conversion
    // if (taskPriority == 2) { }             // ERROR: can't compare to int
    // int result = taskPriority + 1;         // ERROR: no arithmetic with int
    
    // To get the underlying value, you need explicit conversion:
    int priorityValue = static_cast<int>(taskPriority);
    std::cout << "Priority value: " << priorityValue << std::endl;
    
    return 0;
}

Output:

Priority value: 2

Comparing enum classes

You can compare enum class values of the same type, but not different types:

#include <iostream>

enum class Size { small, medium, large };
enum class Priority { low, medium, high };

int main()
{
    Size shirtSize = Size::medium;
    Size availableSize = Size::large;
    Priority taskPriority = Priority::medium;
    
    // OK: comparing same enum class type
    if (shirtSize == Size::medium)
    {
        std::cout << "Shirt size is medium" << std::endl;
    }
    
    if (availableSize != shirtSize)
    {
        std::cout << "Available size is different" << std::endl;
    }
    
    // ERROR: can't compare different enum class types
    // if (shirtSize == taskPriority) { }  // Won't compile!
    
    return 0;
}

Output:

Shirt size is medium
Available size is different

Specifying underlying types

You can explicitly specify the underlying type for enum classes:

#include <iostream>

// Use char as underlying type
enum class Status : char
{
    active = 'A',
    inactive = 'I',
    pending = 'P'
};

// Use short as underlying type  
enum class ErrorCode : short
{
    success = 0,
    warning = 100,
    error = 200
};

int main()
{
    Status userStatus = Status::active;
    ErrorCode lastError = ErrorCode::success;
    
    // Explicit conversion shows the underlying values
    std::cout << "Status: " << static_cast<char>(userStatus) << std::endl;
    std::cout << "Error: " << static_cast<short>(lastError) << std::endl;
    
    return 0;
}

Output:

Status: A
Error: 0

Practical example: Game state management

#include <iostream>

enum class GameState
{
    mainMenu,
    characterSelect,
    loading,
    playing,
    paused,
    gameOver
};

enum class PlayerState
{
    idle,
    walking,
    running,
    jumping,
    attacking
};

class GameManager
{
public:
    void updateGame()
    {
        switch (currentGameState)
        {
        case GameState::mainMenu:
            handleMainMenu();
            break;
        case GameState::playing:
            handleGameplay();
            break;
        case GameState::paused:
            handlePause();
            break;
        case GameState::gameOver:
            handleGameOver();
            break;
        default:
            break;
        }
    }
    
    void setGameState(GameState newState)
    {
        currentGameState = newState;
        std::cout << "Game state changed" << std::endl;
    }
    
    void setPlayerState(PlayerState newState)
    {
        currentPlayerState = newState;
        std::cout << "Player state changed" << std::endl;
    }
    
private:
    GameState currentGameState = GameState::mainMenu;
    PlayerState currentPlayerState = PlayerState::idle;
    
    void handleMainMenu() { /* menu logic */ }
    void handleGameplay() { /* game logic */ }
    void handlePause() { /* pause logic */ }
    void handleGameOver() { /* game over logic */ }
};

int main()
{
    GameManager game;
    
    // Clear separation between different enum types
    game.setGameState(GameState::playing);
    game.setPlayerState(PlayerState::running);
    
    // This would be a compile error (type safety):
    // game.setGameState(PlayerState::running);  // ERROR!
    
    return 0;
}

Output:

Game state changed
Player state changed

Using enum class with switch statements

#include <iostream>

enum class Weather
{
    sunny,
    cloudy,
    rainy,
    snowy,
    stormy
};

std::string weatherToString(Weather weather)
{
    switch (weather)
    {
    case Weather::sunny:  return "sunny";
    case Weather::cloudy: return "cloudy";
    case Weather::rainy:  return "rainy";
    case Weather::snowy:  return "snowy";
    case Weather::stormy: return "stormy";
    default:              return "unknown";
    }
}

void planActivity(Weather weather)
{
    std::cout << "Weather is " << weatherToString(weather) << ". ";
    
    switch (weather)
    {
    case Weather::sunny:
        std::cout << "Perfect for outdoor activities!" << std::endl;
        break;
    case Weather::rainy:
    case Weather::stormy:
        std::cout << "Stay indoors and read a book." << std::endl;
        break;
    case Weather::snowy:
        std::cout << "Time for skiing or snowball fights!" << std::endl;
        break;
    case Weather::cloudy:
        std::cout << "Good for a walk in the park." << std::endl;
        break;
    }
}

int main()
{
    Weather today = Weather::sunny;
    Weather tomorrow = Weather::rainy;
    
    planActivity(today);
    planActivity(tomorrow);
    
    return 0;
}

Output:

Weather is sunny. Perfect for outdoor activities!
Weather is rainy. Stay indoors and read a book.

When to use enum class vs enum

Use enum class when:

  • You want type safety (prevent implicit conversions)
  • You need to avoid naming conflicts
  • You're writing new code (modern C++ best practice)
  • You want clear scoping of enumerators

Use traditional enum when:

  • You need implicit conversion to integers for legacy code
  • You're working with C-style APIs
  • You need the enumerators in the global scope for some reason

Best practices for enum classes

1. Always prefer enum class for new code

// Modern C++ style
enum class Color { red, green, blue };

// Avoid in new code unless specifically needed
enum LegacyColor { red, green, blue };

2. Use descriptive names

// Good
enum class NetworkState { disconnected, connecting, connected, error };

// Less clear
enum class State { a, b, c, d };

3. Consider explicit underlying types when appropriate

// When you need specific sizes or values
enum class HttpStatus : int { ok = 200, notFound = 404, serverError = 500 };

// When you need smaller memory footprint
enum class Direction : char { north, south, east, west };

4. Provide conversion functions when needed

enum class Level { beginner, intermediate, advanced };

std::string levelToString(Level level)
{
    switch (level)
    {
    case Level::beginner:    return "Beginner";
    case Level::intermediate: return "Intermediate";
    case Level::advanced:    return "Advanced";
    default:                 return "Unknown";
    }
}

Key concepts to remember

  1. Enum classes provide better type safety than unscoped enums.

  2. Enumerators must be accessed with scope resolution (EnumName::enumerator).

  3. No implicit conversion to integers - use static_cast for explicit conversion.

  4. Can't compare different enum class types - prevents logical errors.

  5. Can specify underlying types for specific requirements.

  6. Prefer enum class for new code - it's the modern C++ best practice.

Summary

Scoped enumerations (enum classes) are the modern, safer alternative to traditional unscoped enumerations. They solve the problems of naming conflicts and implicit conversions by requiring explicit scope resolution and preventing automatic conversion to integers. While they require slightly more typing, they provide much better type safety and help prevent bugs. Always prefer enum classes for new code unless you specifically need the behavior of unscoped enums.

Quiz

  1. What are the main problems that enum classes solve compared to traditional enums?
  2. How do you access an enumerator in an enum class?
  3. Can you implicitly convert an enum class to an integer? How about explicitly?
  4. Can you compare enumerators from different enum classes? Why or why not?
  5. When might you still choose a traditional enum over an enum class?

Practice exercises

Try these exercises with enum classes:

  1. Create enum classes for card suits and ranks, then create a playing card struct that uses both.
  2. Create an enum class for user permissions (read, write, execute) and write functions to check and modify permissions.
  3. Create enum classes for different types of vehicles and their states, ensuring type safety between them.
  4. Convert a traditional enum from a previous exercise to an enum class and update all the code that uses it.

Continue Learning

Explore other available lessons while this one is being prepared.

View Course

Explore More Courses

Discover other available courses while this lesson is being prepared.

Browse Courses

Lesson Discussion

Share your thoughts and questions

💬

No comments yet. Be the first to share your thoughts!

Sign in to join the discussion