Coming Soon
This lesson is currently being developed
Scoped enumerations (enum classes)
Use modern enum classes for type safety.
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
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
-
Enum classes provide better type safety than unscoped enums.
-
Enumerators must be accessed with scope resolution (EnumName::enumerator).
-
No implicit conversion to integers - use static_cast for explicit conversion.
-
Can't compare different enum class types - prevents logical errors.
-
Can specify underlying types for specific requirements.
-
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
- What are the main problems that enum classes solve compared to traditional enums?
- How do you access an enumerator in an enum class?
- Can you implicitly convert an enum class to an integer? How about explicitly?
- Can you compare enumerators from different enum classes? Why or why not?
- When might you still choose a traditional enum over an enum class?
Practice exercises
Try these exercises with enum classes:
- Create enum classes for card suits and ranks, then create a playing card struct that uses both.
- Create an enum class for user permissions (read, write, execute) and write functions to check and modify permissions.
- Create enum classes for different types of vehicles and their states, ensuring type safety between them.
- Convert a traditional enum from a previous exercise to an enum class and update all the code that uses it.
Explore More Courses
Discover other available courses while this lesson is being prepared.
Browse CoursesLesson Discussion
Share your thoughts and questions