Enums are a compact way to represent a small set of related values using readable names instead of raw numbers. In this tutorial, you’ll see C++ enum and enum class explained for beginners with clear examples, practical tips, and the common mistakes to avoid. By the end, you’ll know when to use unscoped enums (enum), when to prefer scoped enums (enum class), how to convert between enums and integers, and how to design safe flag-style enums.
- Prefer enum class for scoped names and no implicit int conversions.
- Use unscoped enum only for legacy/C interop or when implicit ints are required.
- For flags, choose an unsigned underlying type and define |, &, ~ operators.
- Convert explicitly with static_cast or use std::to_underlying (C++23).
What is an enum in C++ and why use it?
An enum (short for enumeration) is a user-defined type that lists a set of named integer constants. You use an enum to make code more readable and less error-prone than using “magic numbers.”
Classic (unscoped) enums expose their names directly in the surrounding scope and can implicitly convert to integers. That’s convenient but can lead to name collisions and accidental conversions.
Simple unscoped enum example
#class="cd-package">include <iostream>
enum Day { Mon, Tue, Wed, Thu, Fri, Sat, Sun };
int main() {
Day today = Wed;
if (today == Wed) {
std::cout << "It039;s Wednesdayn";
}
// Unscoped enum converts to int implicitly:
std::cout << "Numeric value: " << today << "n";
return 0;
}It's Wednesday
Numeric value: 2Why use it?
- Improves readability (Mon, Tue, Wed) over integers (0, 1, 2).
- Makes switch statements and conditions clearer.
- Groups related values under one type.
What is enum class in C++ (scoped enums)?
A scoped enum is declared with enum class (or enum struct). It fixes the two main problems of classic enums:
- Names are scoped to the enum type (you write
Color::Red, notRed). - No implicit conversion to integers, which prevents accidental mixes and wrong overload calls.
Scoped enum (enum class) example
#class="cd-package">include <iostream>
enum class Color { Red, Green, Blue };
int main() {
Color c = Color::Green;
if (c == Color::Green) {
std::cout << "Green selectedn";
}
// Must cast to print its underlying integer value:
std::cout << "Underlying value: " << static_cast<int>(c) << "n";
return 0;
}Green selected
Underlying value: 1Scoped enums are the modern, safer default in C++. Most style guides and tools recommend using enum class unless you have a specific reason to pick an unscoped enum.
C++ enum vs enum class: key differences
The following table summarizes the most important points in the c++ enum vs enum class comparison:
| Aspect | enum (unscoped) | enum class (scoped) |
|---|---|---|
| Enumerator names | Leak into surrounding scope (e.g., Red) |
Stay inside the enum (e.g., Color::Red) |
| Implicit conversion to int | Allowed (can silently become an int) | Not allowed (requires static_cast) |
| Default underlying type | Implementation-defined integral type | int (unless you specify another) |
| Forward declaration | Requires fixed underlying type (e.g., enum E : int;) |
Allowed with or without fixed type (e.g., enum class E;) |
| Type safety and overload resolution | Weaker; can mix with integers by accident | Stronger; prevents accidental mixes |
| Flags/bitmasks | Bitwise ops work via int conversions (not type-safe) | Define bitwise operators explicitly (type-safe pattern) |
| Modern best practice | Use mainly for legacy/interop | Prefer by default |
Declaring, initializing, and setting custom values
You can assign specific integer values to enumerators and choose an explicit underlying type (useful for ABI size, serialization, or flags). This also helps with the long-tail question “how to choose underlying type for enum class in C++.”
Custom values and underlying type
#class="cd-package">include <iostream>
enum Status { Ok = 0, Warning = 1, Error = 2 }; // unscoped enum
enum class Mode : unsigned {
Read = 1u << 0,
Write = 1u << 1,
Exec = 1u << 2
};
int main() {
Status s = Warning; // OK: unscoped
Mode m = Mode::Read; // Must qualify with Mode::
std::cout << "Status: " << s << "n"; // implicit int
std::cout << "Mode::Read value: " << static_cast<unsigned>(m) << "n";
return 0;
}Status: 1
Mode::Read value: 1Guidelines for choosing an underlying type:
- Default to
enum classwithout specifying a type unless you need control. - Pick an unsigned type (e.g.,
unsigned,std::uint32_t) for bit flags. - Pick a small fixed width type (e.g.,
std::uint8_t) for compact storage in arrays/structs. - Use
intif size and ABI are not a concern and values are small.
Converting between enums and integers
Beginners often ask: how to convert enum to int in C++? With unscoped enums, conversion is automatic. With scoped enums, you must be explicit using static_cast. When converting from int to enum, check the range to avoid invalid values.
Converting enum class to int and back (safely)
#class="cd-package">include <iostream>
enum class Level : int { Low = 0, Medium = 1, High = 2 };
bool is_valid_level(int x) {
return x >= static_cast<int>(Level::Low) &&
x <= static_cast<int>(Level::High);
}
int main() {
Level a = Level::Medium;
// enum class to int:
int ai = static_cast<int>(a);
std::cout << "a as int: " << ai << "n";
// int to enum class (with range check):
int x = 2;
Level b = is_valid_level(x) ? static_cast<Level>(x) : Level::Low;
std::cout << "b as int: " << static_cast<int>(b) << "n";
return 0;
}a as int: 1
b as int: 2Tip: In modern C++ (C++23), std::to_underlying(e) provides a clean, safe way to get the underlying value of an enum. It’s in <utility>, but you’ll need a compiler/library that supports C++23.
Flags and bitmasks with enum class (recommended pattern)
Flags are a common use case: you want to combine multiple options with bitwise operators (|, &, ~). With enum class, define these operators yourself so the code stays type-safe and readable.
Type-safe flags example with enum class
#class="cd-package">include <iostream>
enum class Permission : unsigned {
None = 0u,
Read = 1u << 0,
Write = 1u << 1,
Execute = 1u << 2
};
constexpr Permission operator|(Permission a, Permission b) {
return static_cast<Permission>(
static_cast<unsigned>(a) | static_cast<unsigned>(b)
);
}
constexpr Permission operator&(Permission a, Permission b) {
return static_cast<Permission>(
static_cast<unsigned>(a) & static_cast<unsigned>(b)
);
}
constexpr Permission operator~(Permission a) {
return static_cast<Permission>(
~static_cast<unsigned>(a)
);
}
bool has(Permission value, Permission flag) {
return static_cast<unsigned>(value & flag) != 0u;
}
int main() {
Permission p = Permission::Read | Permission::Write;
std::cout << "Has Read: " << (has(p, Permission::Read) ? "yes" : "no") << "n";
std::cout << "Has Execute: " << (has(p, Permission::Execute) ? "yes" : "no") << "n";
Permission q = p & ~Permission::Write;
std::cout << "After removing Write, has Write: "
<< (has(q, Permission::Write) ? "yes" : "no") << "n";
return 0;
}Has Read: yes
Has Execute: no
After removing Write, has Write: noWhy use this approach?
- Type-safe: You can’t accidentally combine unrelated enum types or mix with raw ints.
- Clear intent: The operators show you’re working with flags.
- Control over size: Using
unsigned(or a fixed-width type) avoids sign issues and sizes the mask appropriately.
- Default to enum class; use unscoped only for C/ABI or implicit-int needs.
- Pick an underlying type when size, interop, or flags matter (unsigned for bitmasks).
- Name the type and enumerators clearly; avoid ambiguous or colliding names.
- Consider adding a
None/Invalid= 0 value where it makes sense. - For flags, implement
|,&,~and a helper likehas(value, flag). - Provide safe conversions: use
static_castorstd::to_underlying(C++23). - Validate integer-to-enum conversions; guard with range checks or a
switchdefault. - Keep enums cohesive: one concept per enum type.
Common mistakes (and how to avoid them)
- Name collisions with unscoped enums: If you declare
enum Color { Red }and laterenum Traffic { Red }in the same scope, you’ll clash. Fix: Preferenum classto keep names scoped. - Accidental int conversions (unscoped enums): Passing an enum where an
intis expected can pick the wrong overload. Fix: Useenum classor cast explicitly. - Forgetting to qualify names:
Color::Redis required forenum class. Fix: Always use theType::Enumeratorform. - Mixing different enums:
Color::Red == TrafficLight::Redwon’t compile withenum class(good!). Fix: Don’t mix different concepts; cast only when truly needed. - Flags without operators for
enum class:Permission::Read | Permission::Writewon’t compile unless you defineoperator|,operator&, etc. Fix: Add the operators as shown above. - Unclear underlying types: Relying on defaults can surprise you across compilers. Fix: Specify the underlying type when size/ABI matters.
When to choose enum vs enum class
“When to use enum class instead of enum in C++?” is a frequent beginner question. Use this quick guide:
- Prefer enum class by default for type safety and clean scoping.
- Use unscoped enum only when you absolutely need implicit conversion or backward/ABI compatibility with C APIs.
- For flags, use
enum classwith an unsigned underlying type and define bitwise operators.
|, &, ~.
std::uint8_t, std::uint32_t).
People also ask (FAQ)
What is an enum in C++ and why use it?
An enum is a type that lists a small set of named constants. It makes code more readable and less error-prone than raw integers. Use it when you have a limited, known set of options (days, states, modes).
What is the difference between enum and enum class in C++?
Unscoped enum leaks names into the surrounding scope and converts to int implicitly. Scoped enum class keeps names inside the enum and forbids implicit conversion, improving type safety and preventing accidental misuse.
How do I declare and initialize an enum in C++?
Unscoped: enum Status { Ok, Warning, Error }; then Status s = Warning;. Scoped: enum class Color { Red, Green }; then Color c = Color::Red;. For printing numbers from enum class, cast: static_cast<int>(c).
When should I prefer enum class over enum in C++?
Prefer enum class almost always: it is safer, avoids naming conflicts, and makes intent clear. Consider unscoped enums only for legacy interop or when implicit int conversion is necessary.
Can I set custom values and types for enum class in C++?
Yes. Use a colon to choose the underlying type, and assign enumerator values explicitly. Example: enum class Mode : unsigned { Read = 1u, Write = 2u };.
Practice: a simple C++ enum class example for beginners
Here is a small end-to-end example showing declaration, comparison, and printing with a cast.
#class="cd-package">include <iostream>
enum class TrafficLight : int { Red = 0, Yellow = 1, Green = 2 };
int main() {
TrafficLight t = TrafficLight::Red;
if (t == TrafficLight::Red) {
std::cout << "Stop!n";
}
std::cout << "Numeric code: " << static_cast<int>(t) << "n";
return 0;
}Stop!
Numeric code: 0Pro tips and modern features
- Explicit underlying type:
enum class E : std::uint8_t { ... };is great for compact storage. - Forward declarations: You can forward-declare scoped enums:
enum class Token;, then define them later. For unscoped, you must specify the underlying type:enum Code : int;. - C++20 “using enum”:
using enum Color;can bring enumerators into scope (reducesColor::noise). Use with care and verify compiler support. - C++23 helpers:
std::to_underlying(e)andstd::is_scoped_enum<T>make code cleaner and safer when working with enums.
enum class. Reach for unscoped enum only when you need legacy-style behavior or implicit integer conversions.
Difference between enum and enum class in C++ with examples
This page has already shown multiple examples. If you only remember one rule, remember this: “Prefer enum class for safety; reach for unscoped enum only when you need legacy-style behavior.”
Next steps
You’ve now seen C++ enum and enum class explained for beginners with syntax, examples, and best practices. Continue your learning journey in the full C++ track on CodDesire:
Explore more C++ tutorials on CodDesire
Sources / Further reading
- C++ Enums (language reference): cppreference.com/cpp/language/enum
- std::to_underlying (C++23): cppreference.com/cpp/utility/to_underlying
- std::is_scoped_enum (C++23): isocppreference.com/w/cpp/types/is_scoped_enum.html
- C++ Core Guidelines (Enums): isocpp.org/guidelines
- clang-tidy: Prefer enum class: clang.llvm.org
- MSVC warning C26812 (Prefer enum class): learn.microsoft.com


