Is this content useful for you? Support this site by donating any amount....(takes you to Paypal website).
Other Amount

Categories

Using C Cpp Enums Effectively - Banner jpg
C Programming, Free-Pascal, Programming

C/C++ Enums – Using them Effectively

Read Time:5 Minute, 29 Second

Enums are one of the simplest yet most misunderstood features in C and C++. They look harmless, almost trivial, but the way you use them can dramatically affect type‑safety, maintainability, and clarity in your codebase. Modern C++ gives us powerful enum features that go far beyond the classic C‑style enums — and using them effectively is a skill worth mastering.

In this article, we’ll explore what enums are, how C and C++ differ in their treatment, why reverse lookup is a recurring problem, and how to solve it cleanly using encapsulation and templates.

What Are Enums?

An enum (enumeration) is a user‑defined type consisting of a set of named integral constants. They make code more readable and reduce the risk of using “magic numbers”.

Classic C‑style enum

enum Color { Red, Green, Blue };

Under the hood:

  • Red == 0
  • Green == 1
  • Blue == 2

These values are implicitly convertible to integers — which is both convenient and dangerous.

C and C++ Enums — A Bit of History

C++ inherited enums directly from C, which itself was influenced over time by Pascal’s strong typing and clean enumeration semantics. But C++ eventually improved on the concept by introducing scoped enums (enum class), which fix many of the original problems.

C‑style enums (unscoped)

  • Implicitly convert to int
  • All enumerators share the same namespace
  • Easy to misuse

C++11 scoped enums (enum class)

  • No implicit conversion to integers
  • Strongly typed
  • Enumerators are scoped (e.g., ToolKind::Select)

This makes them safer and more expressive.

Simple Enum Examples

Here’s a practical example using both classic and modern enums:

#include <iostream>
#include <unordered_map>

using namespace std;

enum class ToolKind
{
    Select,
    Orbit,
    Box,
    Cylinder,
    Sphere,
    Cone,
    Torus,
    Wedge,
    Prism,
    Revolve,
    Line,
    Polyline,
    Arc,
    Circle,
    Ellipse,
    Rectangle,
    Spline
};

static const std::unordered_map<int, std::string> ReverseMap =
{
    {0, "Select"},
    {1, "Orbit" },
    {8, "Prism"}
};

std::string ReverseLookup(ToolKind in) {
    string res;
    auto it = ReverseMap.find(int(in));
    if (it != ReverseMap.end()) {
        res = it->second;
    }
    else {
        res = "";
    }
    return res;
}

int main() {
    cout << "hello world" << endl;
    enum tooli { p, q, r };
    tooli tt = tooli::q;
    cout << "plain one is: " << tt << endl;

    enum struct toolk { one , two, three };
    toolk ss = toolk::three;
    cout << "struct/class one is: " << int(ss) << endl;
 
    ToolKind uu = ToolKind::Prism;
    cout << "ToolKind is: " << ReverseLookup(uu) << endl;

    return 0;
}

This demonstrates:

  • Classic enums (tooli)
  • Scoped enums (toolk)
  • A reverse lookup using unordered_map

The Reverse Lookup Problem

Enums are great for mapping names → values, but the reverse (value → name) is not built in.

Why?

  • Enums are not reflection‑enabled
  • They do not store their own names
  • The compiler discards identifier strings

This means you must manually maintain a reverse lookup table, which becomes error‑prone as enums grow.

A Clean Solution: Encapsulate Reverse Lookup

Instead of scattering lookup tables across your code, you can encapsulate the logic using a template class. This keeps enum‑to‑string mappings organized and reusable.

Encapsulated Enum Manager

#include <iostream>
#include <map>
#include <initializer_list>
#include <utility> 
#include <string>

template <typename EEnumType>
class EnumManager {
    private:
        std::map<EEnumType, std::string> ReverseMap;

    public:
        EnumManager(std::initializer_list<std::pair<EEnumType, std::string>> items) {
            for (const auto& item : items) {
                ReverseMap.insert(item);
            }
        }      

        std::string ReverseLookup(EEnumType in) const {
            auto it = ReverseMap.find(in);
            if (it != ReverseMap.end()) {
                return it->second;
            }
            return "";
        }
};

enum struct ToolKind
{
    Select,
    Orbit,
    Box,
    Cylinder,
    Sphere,
    Cone,
    Torus,
    Wedge,
    Prism,
    Revolve,
    Line,
    Polyline,
    Arc,
    Circle,
    Ellipse,
    Rectangle,
    Spline
};

int main() {
    ToolKind uu = ToolKind::Prism;
    
    EnumManager<ToolKind> manager({
        {ToolKind::Prism, "Prism"},
        {ToolKind::Select, "Select"}
    });

    std::cout << "ToolKind is: " << manager.ReverseLookup(uu) << std::endl;
    return 0;
}

Why this approach works well

  • Each enum type gets its own manager
  • No global state
  • Easy to extend
  • Type‑safe
  • Works with scoped enums

This is a clean, modern C++ approach that avoids macros and keeps your code maintainable.

Conclusion

Enums are far more powerful than they appear at first glance. Classic C‑style enums are simple but risky, while modern C++ scoped enums give you type‑safety and clarity. The biggest missing feature — reverse lookup — can be solved elegantly using encapsulation and templates.

If you use enums heavily in your codebase, investing in a reusable enum manager is absolutely worth it.

Further Reading

Here are some excellent resources to deepen your understanding:

Leave a Reply

Your email address will not be published. Required fields are marked *