C++ Enum vs Enum Class: Explained for Beginners

C++ Enum vs Enum Class: Explained for Beginners


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.

Key takeaways: C++ enum and enum class explained for beginners

  • 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?

C++ enum and enum class explained for beginners: scoping and type-safety visual comparison
Scoped enum class prevents name clashes and unsafe int conversions.

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

Code
#class="cd-package">include <iostream>

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

int main() {
    Day today = Wed;

    if (today == Wed) {
        std::cout << "It&#039;s Wednesdayn";
    }

    // Unscoped enum converts to int implicitly:
    std::cout << "Numeric value: " << today << "n";
    return 0;
}
Output
It's Wednesday
Numeric value: 2

Why 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)?

C++ enum and enum class explained for beginners: underlying type and memory size illustration
Choosing an underlying type makes enum class compact and precise.

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, not Red).
  • No implicit conversion to integers, which prevents accidental mixes and wrong overload calls.

Scoped enum (enum class) example

Code
#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;
}
Output
Green selected
Underlying value: 1

Scoped 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

Code
#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;
}
Output
Status: 1
Mode::Read value: 1

Guidelines for choosing an underlying type:

  • Default to enum class without 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 int if 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)

Code
#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;
}
Output
a as int: 1
b as int: 2

Tip: 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

Code
#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;
}
Output
Has Read: yes
Has Execute: no
After removing Write, has Write: no

Why 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.
Checklist: design a safe, beginner-friendly enum
  • 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 like has(value, flag).
  • Provide safe conversions: use static_cast or std::to_underlying (C++23).
  • Validate integer-to-enum conversions; guard with range checks or a switch default.
  • 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 later enum Traffic { Red } in the same scope, you’ll clash. Fix: Prefer enum class to keep names scoped.
  • Accidental int conversions (unscoped enums): Passing an enum where an int is expected can pick the wrong overload. Fix: Use enum class or cast explicitly.
  • Forgetting to qualify names: Color::Red is required for enum class. Fix: Always use the Type::Enumerator form.
  • Mixing different enums: Color::Red == TrafficLight::Red won’t compile with enum class (good!). Fix: Don’t mix different concepts; cast only when truly needed.
  • Flags without operators for enum class: Permission::Read | Permission::Write won’t compile unless you define operator|, 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 class with an unsigned underlying type and define bitwise operators.
Decision flow: choose enum or enum class
Do you need implicit int conversion or C API compatibility?

Yes
Use unscoped enum. Keep names short, beware of overloads and name collisions.

No
Choose enum class for safety, scoped names, and clearer intent.

Need flags/bitmasks? Pick an unsigned underlying type and define |, &, ~.

Need fixed size/ABI or serialization? Specify a fixed-width type (e.g., 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.

Code
#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;
}
Output
Stop!
Numeric code: 0

Pro 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 (reduces Color:: noise). Use with care and verify compiler support.
  • C++23 helpers: std::to_underlying(e) and std::is_scoped_enum<T> make code cleaner and safer when working with enums.
Remember: Prefer 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

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted