What an enum Is For
A struct groups several related values into one object. An enum solves a different problem: it gives names to a small, fixed set of choices. Instead of remembering that 0 means "red", 1 means "green", and 2 means "blue", you write Color::Red and the compiler keeps you honest.
Reaching for an enum whenever a variable can only be one of a handful of named states - a traffic light color, a card suit, a connection status - makes code self-documenting and lets the compiler catch typos and missing cases that bare integers never would.
Declaring an enum
Modern C++ has two flavors. Start with the one you should reach for almost always: the scoped enum class. You list the names, and each enumerator is accessed through the enum's name with :::
Notice you write Color::Green, never a bare Green. The values themselves are just labels - you compare them, assign them, and pass them around, but you rarely care about the underlying number. By default Red is 0, Green is 1, and Blue is 2, counting up from zero.
Plain enum vs enum class
The older, unscoped enum (no class keyword) drops its names directly into the surrounding scope and converts to int on its own. That sounds convenient, but it causes two real problems:
enum Color { Red, Green, Blue };
enum Fruit { Apple, Banana, Red }; // error: 'Red' already declared
enum Status { Active, Inactive };
int x = Active; // compiles silently - is this what you meant?
if (Active == Banana) { // compares unrelated enums via int - allowed!
}
Because plain enumerators are global names, two enums can collide just by sharing a label. And because they decay to int, the compiler happily compares values from completely unrelated enums. A scoped enum class fixes both: the names live inside the type, and the type will not silently turn into an int:
The rule of thumb: use enum class by default. Only fall back to a plain enum when you specifically want the implicit int conversion, such as old C-style flag constants.
Custom Values and the Underlying Type
You can assign explicit numbers to enumerators. Any you leave out continue counting up from the previous one, which is handy for things like HTTP status codes or bit flags:
Every enum is backed by an integer type - int by default. You can pin it to a smaller type when you care about size, for example storing many enums in a packed structure or matching a wire format:
Choosing the underlying type also guarantees the range your values must fit into - a uint8_t enum cannot hold a value above 255.
Converting Between enum and int
A scoped enum class never converts implicitly, which is the whole point. When you genuinely need the number - to print it, index an array, or read it from a file - reach for static_cast. Going from enum to int is always safe:
Converting an int back to an enum is the dangerous direction. The cast does not check that the number corresponds to a real enumerator - it will hand you a value that is technically outside the enum's named set:
Suit s = static_cast<Suit>(2); // fine - that's Clubs
Suit bad = static_cast<Suit>(99); // compiles, but 99 is not a valid Suit
// using `bad` in a switch or as an array index is a lurking bug
If the integer comes from user input or a file, validate the range yourself before casting, or you will create a value no case ever handles - a subtle source of undefined behavior down the line.
Using enums with switch
Because an enum is "one of a fixed set", it pairs perfectly with a switch. When you cover every enumerator, many compilers will warn you if you later add a new value and forget to handle it - free safety you do not get from raw integers:
One gotcha: there is no built-in way to print the name of an enumerator. cout << TrafficLight::Red will not compile for a scoped enum, and even for a plain enum it prints the number, not "Red". A small switch or lookup table like the one above is the usual way to turn an enum into a human-readable string.
Next: Exceptions
Enums and structs let you model what your data looks like. But real programs also have to deal with things going wrong - a file that won't open, a number that won't parse, a value out of range. C++ handles those failure paths with exceptions, and that is the next page.
Frequently Asked Questions
What is the difference between enum and enum class in C++?
A plain enum leaks its names into the surrounding scope and implicitly converts to int, which causes name clashes and accidental comparisons. A scoped enum class keeps its names inside the enum (Color::Red) and refuses to convert to int without an explicit cast. Prefer enum class in modern C++ - it is type-safe and avoids the classic gotchas.
How do you convert a C++ enum to an int?
A plain enum converts implicitly, so int n = Red; just works. A scoped enum class requires an explicit cast: int n = static_cast<int>(Color::Red);. To go the other way, cast the int back: Color c = static_cast<Color>(2); - but be careful, the runtime does not check that the value is a valid enumerator.
What value does the first C++ enum start at?
By default the first enumerator is 0, and each one after it is one greater than the previous. So in enum class Level { Low, Mid, High };, Low is 0, Mid is 1, and High is 2. You can assign explicit values to any of them to override this.