Enums & enum class

Reviewed & published by Brayan K

An enum gives friendly names to a fixed set of related values — directions, days, states, status codes. Instead of scattering magic numbers like 0, 1, 2 through your code, you write Color::Red. This lesson covers plain enums, the safer modern enum class, custom values, and how to print them.

Part of the free C++ course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

An enum is like the gear selector in a car: P, R, N, D. There's a small, fixed set of valid positions, each with a clear name. You'd never label them "0, 1, 2, 3" on the dashboard — the words prevent mistakes. A plain enum is like a loose lever that can slip into a number you didn't intend; an enum class is a precise gate that only lets you select a named, valid position. That extra safety is why modern C++ favors enum class.

1. Plain (Unscoped) Enums

The classic enum defines named constants that start at 0 and count up. They convert to int automatically and their names live in the surrounding scope. That convenience is also their weakness — names can clash and values get compared to raw numbers by accident.

#include <iostream>
using namespace std;

// A plain (unscoped) enum: names leak into this scope.
enum Direction { North, East, South, West };

int main() {
    Direction d = East;

    // Plain enums convert to int automatically.
    cout << "value: " << d << '\n';   // value: 1  (North=0, East=1, ...)

    if (d == 1) cout << "d equals 1\n"; // works (but is this a bug magnet?)

    // Names are in the enclosing scope -> you write East, not Direction::East.
    Direction start = North;
    cout << "start: " << start << '\n'; // start: 0
    return 0;
}

// ✅ Expected output:
//    value: 1
//    d equals 1
//    start: 0

2. The Safer enum class

A scoped enum keeps its names behind the type name (Color::Red) and refuses to convert to int without an explicit cast. This prevents accidental comparisons and name clashes, which is why it's the recommended default in modern C++.

#include <iostream>
using namespace std;

// A scoped enum: safer, no implicit int conversion.
enum class Color { Red, Green, Blue };

int main() {
    Color c = Color::Green;     // must qualify with Color::

    // cout << c;               // ERROR: no implicit conversion to int
    cout << "as int: " << static_cast<int>(c) << '\n'; // 1

    if (c == Color::Green) cout << "it's green\n"; // it's green

    // No name clash: another enum can also have a "Red".
    // enum class Fruit { Apple, Red };  // fine, Fruit::Red is distinct
    return 0;
}

// ✅ Expected output:
//    as int: 1
//    it's green

3. Custom Underlying Values

You can assign explicit numbers to enum members — perfect for things like HTTP status codes or error codes that have meaningful values. Any members you leave unspecified keep counting up from the previous one.

#include <iostream>
using namespace std;

// Explicit values, e.g. HTTP status codes.
enum class HttpStatus {
    Ok        = 200,
    Created   = 201,
    NotFound  = 404,
    Error     = 500
};

int main() {
    HttpStatus s = HttpStatus::NotFound;
    cout << "code: " << static_cast<int>(s) << '\n'; // code: 404

    // Unspecified values keep counting from the previous one.
    enum class Level { Low = 1, Medium, High }; // 1, 2, 3
    cout << "High = " << static_cast<int>(Level::High) << '\n'; // High = 3
    return 0;
}

// ✅ Expected output:
//    code: 404
//    High = 3

4. Printing Enums with a switch

Since an enum stores a number, not a word, cout can't print a readable name on its own. The standard pattern is a small function that takes the enum and returns a std::string via a switch — clear, fast, and easy to extend.

#include <iostream>
#include <string>
using namespace std;

enum class Day { Mon, Tue, Wed, Thu, Fri, Sat, Sun };

// Map an enum to a readable name with a switch.
string name(Day d) {
    switch (d) {
        case Day::Mon: return "Monday";
        case Day::Tue: return "Tuesday";
        case Day::Wed: return "Wednesday";
        case Day::Thu: return "Thursday";
        case Day::Fri: return "Friday";
        case Day::Sat: return "Saturday";
        case Day::Sun: return "Sunday";
    }
    return "?";
}

bool isWeekend(Day d) { return d == Day::Sat || d == Day::Sun; }

int main() {
    Day today = Day::Sat;
    cout << name(today) << " weekend? " << isWeekend(today) << '\n';
    // Saturday weekend? 1
    return 0;
}

// ✅ Expected output:
//    Saturday weekend? 1

5. Choosing the Underlying Type

By default an enum is backed by int. You can pin it to a smaller type — like unsigned char for a single byte — when memory matters or a binary format requires it. Converting a raw number back into the enum needs an explicit static_cast, and you should validate the number first.

#include <iostream>
using namespace std;

// Pin the storage to one byte by choosing the underlying type.
enum class Suit : unsigned char { Clubs, Diamonds, Hearts, Spades };

int main() {
    cout << "size: " << sizeof(Suit) << " byte\n";  // size: 1 byte

    Suit s = Suit::Hearts;
    cout << "Hearts = " << static_cast<int>(s) << '\n'; // Hearts = 2

    // Convert a number back to an enum (validate the range first!).
    int n = 3;
    Suit fromInt = static_cast<Suit>(n);
    cout << "Spades? " << (fromInt == Suit::Spades) << '\n'; // Spades? 1
    return 0;
}

// ✅ Expected output:
//    size: 1 byte
//    Hearts = 2
//    Spades? 1

Your turn. Define a scoped enum and use it:

#include <iostream>
using namespace std;

int main() {
    // 🎯 YOUR TURN — define a scoped enum and use it.

    // 1) Declare an enum class TrafficLight with Red, Yellow, Green
    ___;   // 👉 enum class TrafficLight { Red, Yellow, Green };

    // 2) Make a variable set to Green
    TrafficLight light = ___;   // 👉 TrafficLight::Green

    if (light == TrafficLight::Green) cout << "Go!\n";
    else cout << "Wait\n";
    // ✅ Expected: Go!
    return 0;
}

🧩 Reorder Challenge

These lines define a scoped enum and print one member as an int. Reorder them so the program prints 2.

A.     Size s = Size::Large;
B. enum class Size { Small, Medium, Large };
C.     cout << static_cast<int>(s) << '\n';
D. int main() {
E.     return 0;
F. }
B. enum class Size { Small, Medium, Large };
D. int main() {
A.     Size s = Size::Large;
C.     cout << static_cast<int>(s) << '\n';
E.     return 0;
F. }

Why: the enum class must be declared before main can use it. Inside main you create the variable (B then A), then print it — and because enum class won't convert implicitly, you need the explicit static_cast<int>. Large is the third member (Small=0, Medium=1, Large=2), so it prints 2.

🧠 Quick Recall

Predict the output before revealing each answer.

enum E { A, B, C }; cout << C;

2 — a plain enum converts to int automatically, and C is the third member (A=0, B=1, C=2).

enum class L { X = 5, Y, Z }; cout << static_cast<int>(L::Z);

7 — X is 5, so Y is 6 and Z is 7 (counting continues from the explicit value).

enum class C { Red }; cout << C::Red;

Compile error — an enum class has no implicit conversion to int, so cout can't print it without a static_cast.

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

SyntaxMeaningNote
enum E { A, B, C };plain (unscoped) enumA=0, B=1, C=2; converts to int
enum class E { A, B };scoped enum (preferred)no implicit int conversion, no name leak
E::Aaccess a scoped membermust qualify with the type name
{ Ok = 200, NotFound = 404 }explicit valuesunspecified ones keep counting up
{ Low = 1, Medium, High }auto-incrementMedium=2, High=3
enum class E : unsigned charchoose underlying typepins storage size (here 1 byte)
static_cast<int>(e)enum → intrequired for scoped enums
static_cast<E>(2)int → enumvalidate the range first
sizeof(E)size of an enummatches its underlying type
switch (e) { case E::A: ... }map enum → namereturn a std::string per case
e == E::Acompare enum valuestype-safe with enum class
cout << e;print scoped enumcompile error — cast it first

Mini-Challenge: Order Status Tracker

Define an OrderStatus enum, map it to a label with a switch, and detect terminal states. Match the expected output in the comments.

#include <iostream>
#include <string>
using namespace std;

int main() {
    // 🎯 MINI-CHALLENGE: order status tracker
    // 1. Define  enum class OrderStatus { Pending, Shipped, Delivered, Cancelled };
    // 2. Write a function  string label(OrderStatus s)  that uses a switch
    //    to return a readable name ("Pending", "Shipped", ...).
    // 3. Write  bool isFinal(OrderStatus s)  -> true for Delivered or Cancelled.
    // 4. In main, set a status to Delivered, print its label and whether it's final.
    //
    // Expected output:  Delivered (final: 1)

    // your code here
    return 0;
}

🎉 Lesson Complete

Practice quiz

What is the main difference between a plain enum and an enum class?

  • enum class keeps its names scoped and does not implicitly convert to int; a plain enum leaks names and converts to int automatically
  • enum class cannot have custom values, but a plain enum can
  • A plain enum is the modern, preferred form added in C++11
  • There is no difference; they are interchangeable keywords

Answer: enum class keeps its names scoped and does not implicitly convert to int; a plain enum leaks names and converts to int automatically. A scoped enum class avoids name leaks and accidental int comparisons, which is why modern C++ prefers it.

Given enum class Color { Red, Green, Blue };, how do you print the value of c = Color::Green?

  • cout << c;
  • cout << static_cast<int>(c);
  • cout << Color::c;
  • cout << (int)Color;

Answer: cout << static_cast<int>(c);. An enum class has no implicit conversion to int, so you must cast: static_cast<int>(c). Printing it directly is a compile error.

For enum class Level { Low = 1, Medium, High };, what is static_cast<int>(Level::High)?

  • 1
  • 2
  • 3
  • 0

Answer: 3. Low is 1, so Medium auto-increments to 2 and High to 3.

What does cout << C; print for plain enum E { A, B, C };?

  • A compile error
  • 2
  • C
  • 3

Answer: 2. A plain enum converts to int automatically, and C is the third member (A=0, B=1, C=2).

How do you pin a scoped enum's storage to a single byte?

  • enum class Suit : unsigned char { ... };
  • enum class Suit byte { ... };
  • enum Suit(1) { ... };
  • You cannot change an enum's underlying type

Answer: enum class Suit : unsigned char { ... };. Specifying the underlying type, e.g. : unsigned char, pins the storage size (here 1 byte).

To access a member of enum class Color { Red, Green, Blue };, you must write:

  • Red
  • Color.Red
  • Color::Red
  • Color->Red

Answer: Color::Red. Scoped enum members must be qualified with the type name and ::, so Color::Red.

What is the standard way to turn an enum into a readable name?

  • cout prints the name automatically
  • A function with a switch that returns a std::string per case
  • Calling .name() on the enum value
  • Using static_cast<string>(value)

Answer: A function with a switch that returns a std::string per case. Since an enum stores a number, not text, you write a small function (usually a switch) that returns the right string.

For enum class HttpStatus { Ok = 200, Created, NotFound = 404 };, what is the value of Created?

  • 200
  • 201
  • 1
  • 404

Answer: 201. Unspecified values keep counting up from the previous one, so Created is 201.

Why must you validate a number before static_cast<Color>(n)?

  • The cast will not compile otherwise
  • An out-of-range value produces an enum with no matching name
  • static_cast cannot convert int to an enum at all
  • It automatically clamps to the nearest valid value

Answer: An out-of-range value produces an enum with no matching name. Casting an out-of-range int, like static_cast<Color>(99), compiles but yields an invalid enum value, so you must range-check first.

What is sizeof(Suit) for enum class Suit : unsigned char { Clubs, Diamonds, Hearts, Spades };?

  • 1
  • 2
  • 4
  • 8

Answer: 1. The underlying type is unsigned char, so the enum occupies exactly 1 byte.

Continue this course

Frequently asked questions

What is the difference between enum and enum class?

A plain enum leaks its names into the surrounding scope and converts implicitly to int, which can cause name clashes and accidental comparisons. An enum class (a scoped enum, added in C++11) keeps names inside the enum's scope and does not convert to int automatically. Prefer enum class in modern code for safety.

Why won't enum class print as a word with cout?

An enum stores an integer value, not text, so there is no built-in name-to-string mapping. cout cannot print an enum class at all without a cast, and a plain enum prints the underlying number. To show a readable name you write a small function (often with a switch) that returns the right string.

Can I choose the numbers behind enum values?

Yes. By default values start at 0 and increase by 1, but you can assign explicit values: enum class Status { Ok = 200, NotFound = 404 };. Unspecified values continue counting up from the previous one, which is handy for things like HTTP status codes or bit flags.

How do I control the size of an enum?

You can specify the underlying integer type: enum class Color : unsigned char { Red, Green, Blue };. This pins the storage to one byte, which matters for memory-tight structures or binary protocols. Any integer type is allowed as the base.

How do I convert an enum class to and from an int?

Because there is no implicit conversion, you cast explicitly: static_cast<int>(color) to get the number, and static_cast<Color>(2) to turn a number back into an enum value. Validate the number first, since casting an out-of-range value produces an enum with no matching name.