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
- Declare plain enums and the scoped enum class
- Why enum class is safer (no implicit int conversion, no name leaks)
- Assign custom underlying values like 200, 404, 500
- Map an enum to a readable name with a switch
- Choose the underlying type to control size
- Convert safely between an enum and an int with static_cast
💡 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: 02. 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 green3. 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 = 34. 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? 15. 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? 1Your 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
- 💡 Prefer enum class: it avoids name clashes and accidental int comparisons.
- 💡 Handle every case in a switch: leaving the default off lets the compiler warn about missing members.
- 💡 Give meaningful values when they map to a real protocol (HTTP codes, error numbers).
- 💡 Validate before casting an int back to an enum — an out-of-range number yields an enum with no matching name.
Common Errors (and the fix)
- Printing an enum class directly: won't compile. Cast it: static_cast<int>(c) or write a name function.
- Forgetting the scope: with enum class Color you must write Color::Red, not bare Red.
- Name clashes with plain enums: two unscoped enums both defining Red conflict. Use enum class.
- Assuming a cast is safe: static_cast<Color>(99) compiles but isn't a valid color. Range-check first.
📋 Quick Reference
| Syntax | Meaning | Note |
|---|---|---|
| enum E { A, B, C }; | plain (unscoped) enum | A=0, B=1, C=2; converts to int |
| enum class E { A, B }; | scoped enum (preferred) | no implicit int conversion, no name leak |
| E::A | access a scoped member | must qualify with the type name |
| { Ok = 200, NotFound = 404 } | explicit values | unspecified ones keep counting up |
| { Low = 1, Medium, High } | auto-increment | Medium=2, High=3 |
| enum class E : unsigned char | choose underlying type | pins storage size (here 1 byte) |
| static_cast<int>(e) | enum → int | required for scoped enums |
| static_cast<E>(2) | int → enum | validate the range first |
| sizeof(E) | size of an enum | matches its underlying type |
| switch (e) { case E::A: ... } | map enum → name | return a std::string per case |
| e == E::A | compare enum values | type-safe with enum class |
| cout << e; | print scoped enum | compile 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
- ✅ Plain enum leaks names and converts to int; enum class is scoped and safe
- ✅ Access scoped members with Type::Member
- ✅ Assign custom values; unspecified ones auto-increment
- ✅ Map enums to readable names with a switch
- ✅ Choose the underlying type for size; cast explicitly to/from int
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
- Previous: Type Casting
- Next: Namespaces — Organise code and avoid name clashes
- Quick reference: C++ cheat sheet
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.