Skip to content
C++ Better Explained
Go back
C++ Enum Tutorial: enum and enum class Explained
Edit page

C++ Enum Tutorial: enum and enum class Explained

Imagine tracking the status of a network request. You could represent it as an integer: 0 means pending, 1 means success, 2 means failed. That works, but what happens six months later when someone reads if (status == 2)? They have no idea what 2 means without going back to find the definition.

Enums (enumerations) solve this by giving meaningful names to a set of related integer values. Instead of 2, you write Status::FAILED. That’s readable at a glance.

This tutorial covers C++ enums from scratch — including the modern enum class that you should be using in new code.


Your First enum

Here’s a basic enum:

#include <iostream>
using namespace std;

enum Direction {
    NORTH,
    SOUTH,
    EAST,
    WEST
};

int main() {
    Direction d = NORTH;

    if (d == NORTH) {
        cout << "Heading north!" << endl;
    }
    return 0;
}

By default, NORTH = 0, SOUTH = 1, EAST = 2, WEST = 3. The compiler assigns integers starting from zero, incrementing by one.

You’ve replaced meaningless numbers with readable names. That’s the entire point.


Assigning Custom Values

You don’t have to accept the default 0, 1, 2, 3. You can assign any integer values you want:

enum HttpStatus {
    OK = 200,
    CREATED = 201,
    NOT_FOUND = 404,
    SERVER_ERROR = 500
};

int main() {
    HttpStatus response = OK;
    cout << "Status code: " << response << endl;  // 200
    return 0;
}

This is great for HTTP status codes, error codes, or any domain where specific numbers already have meaning.

You can also set some values and let the rest auto-increment:

enum Priority {
    LOW = 1,
    MEDIUM,    // 2 (auto-incremented)
    HIGH,      // 3
    CRITICAL = 10
};

The Problem with Plain enum

Plain enums have a significant issue: their values pollute the surrounding scope. Consider:

enum Color { RED, GREEN, BLUE };
enum Fruit { APPLE, ORANGE, GREEN };  // Error! GREEN is already defined

GREEN from Color clashes with GREEN from Fruit. This is called name leakage, and it’s a real problem in large codebases.

There’s another issue: plain enums implicitly convert to int. That means this compiles without a warning, even though it makes no sense:

enum Color { RED, GREEN, BLUE };
enum Size { SMALL, MEDIUM, LARGE };

Color c = RED;
int n = c;           // Compiles fine — converts Color to int
bool same = (c == SMALL);  // Compares RED (0) to SMALL (0) — true!

RED == SMALL evaluates to true because they’re both 0 under the hood. That’s a bug waiting to happen.


enum class: The Modern Solution

C++11 introduced enum class (also called scoped enumerations). It solves both problems:

#include <iostream>
using namespace std;

enum class Color {
    RED,
    GREEN,
    BLUE
};

int main() {
    Color c = Color::RED;  // Must use the type prefix

    if (c == Color::RED) {
        cout << "Color is red" << endl;
    }
    return 0;
}

Two key differences from plain enum:

  1. Names are scoped: You write Color::RED, not just RED. No naming conflicts.
  2. No implicit int conversion: int n = Color::RED; is a compile error. You have to cast explicitly.

Use enum class for all new code. It’s safer and clearer.


Using enum class in a switch

Enums and switch statements are a natural pair:

#include <iostream>
using namespace std;

enum class Season {
    SPRING,
    SUMMER,
    AUTUMN,
    WINTER
};

void describe(Season s) {
    switch (s) {
        case Season::SPRING: cout << "Flowers blooming" << endl; break;
        case Season::SUMMER: cout << "Hot and sunny" << endl; break;
        case Season::AUTUMN: cout << "Leaves falling" << endl; break;
        case Season::WINTER: cout << "Cold and dark" << endl; break;
    }
}

int main() {
    describe(Season::AUTUMN);  // Leaves falling
    return 0;
}

A good compiler will warn you if you miss a case in the switch — another benefit of using enums over raw integers.

If you're looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you're a complete beginner or looking to solidify your understanding. Just $19.

Converting enum class to int

Unlike plain enums, enum class won’t auto-convert to int. When you genuinely need the underlying integer value, use static_cast:

enum class Priority {
    LOW = 1,
    MEDIUM = 2,
    HIGH = 3
};

int main() {
    Priority p = Priority::HIGH;
    int value = static_cast<int>(p);  // 3
    cout << "Priority level: " << value << endl;
    return 0;
}

The explicit cast makes it obvious that you’re intentionally treating the enum as an integer, rather than it happening accidentally.


Specifying the Underlying Type

By default, enum values are stored as int. You can specify a different underlying type — useful for saving memory or ensuring a specific size:

enum class Direction : uint8_t {
    NORTH,
    SOUTH,
    EAST,
    WEST
};

uint8_t is an unsigned 8-bit integer (0–255), fine for four directions. You can use any integral type: int, unsigned int, short, long, uint8_t, etc.


enum vs #define vs const

Before enums existed, programmers used #define for named constants:

#define RED 0
#define GREEN 1
#define BLUE 2

Problems: #define is a text substitution done by the preprocessor before compilation. It has no type, no scope, and no safety. const int is better, but it still doesn’t group related values together.

Enums give you:


A Practical Example: Game State Machine

Here’s a real-world pattern you’ll see often — using an enum to represent program state:

#include <iostream>
using namespace std;

enum class GameState {
    MENU,
    PLAYING,
    PAUSED,
    GAME_OVER
};

void update(GameState state) {
    switch (state) {
        case GameState::MENU:      cout << "Showing main menu" << endl; break;
        case GameState::PLAYING:   cout << "Game running..." << endl; break;
        case GameState::PAUSED:    cout << "Game paused" << endl; break;
        case GameState::GAME_OVER: cout << "Game over screen" << endl; break;
    }
}

int main() {
    GameState state = GameState::MENU;
    update(state);

    state = GameState::PLAYING;
    update(state);

    state = GameState::GAME_OVER;
    update(state);

    return 0;
}

The enum makes the state machine’s logic clear and self-documenting. Anyone reading this code immediately understands what’s going on.


Quick Reference

Featureplain enumenum class
Name scopingNo (names leak)Yes (prefix required)
Implicit int conversionYesNo
C++ versionAllC++11+
Recommended?Legacy code onlyYes, for new code


Take Your C++ Further

If you’re looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you’re a complete beginner or looking to solidify your understanding. Just $19.

👉 Get the C++ Better Explained Ebook — $19


Edit page
Share this post on:

Previous Post
C++ User Input: How to Use cin to Read Input
Next Post
C++ Error Messages Explained: What They Mean and How to Fix Them