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:9 Minute, 56 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.

Extended “EnumLookup” Class – Lookup Both Ways

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

template <typename simpleType, typename EnumType, EnumType lastEnumerator>
class EnumLookup {
private:
    std::map<simpleType, EnumType> forwardMap;
    std::map<EnumType, simpleType> reverseMap;

public:
    EnumLookup(std::initializer_list<std::pair<EnumType, simpleType>> reverseItems) {
        for (const auto& item : reverseItems) {
            reverseMap.insert(item);
            const std::pair<simpleType, EnumType> ritem = { item.second, item.first };
            forwardMap.insert(ritem);
        }
    }

    EnumLookup(std::initializer_list<std::pair<simpleType, EnumType>> forwardItems) {
        for (const auto& ritem : forwardItems) {
            forwardMap.insert(ritem);
            const std::pair<EnumType, simpleType> item = { ritem.second, ritem.first };
            reverseMap.insert(item);
        }
    }

    EnumLookup(std::deque<simpleType> in_deque) {
        auto totalElements = static_cast<int>(lastEnumerator);
        if (totalElements != (in_deque.size() - 1)) {
            std::cerr << "ERROR: WRONG SIZE - EnumLookup - TotalElements: " << totalElements << ", deque size: " << in_deque.size() << std::endl;
            return;
        }
        for (int et = 0; et < totalElements; ++et) {
            simpleType c = in_deque.front();
            in_deque.pop_front();
            const std::pair<simpleType, EnumType> item = { c, static_cast<EnumType>(et) };
            const std::pair<EnumType, simpleType> ritem = { static_cast<EnumType>(et), c };
            forwardMap.insert(item);
            reverseMap.insert(ritem);
            //std::cout << ".. new deque size: " << in_deque.size() << std::endl ;
        }
    }

    simpleType toSimpleType(EnumType in) const {
        auto it = reverseMap.find(in);
        if (it != reverseMap.end()) {
            return it->second;
        }
        return simpleType();
    };

    EnumType toEnumType(simpleType in) {
        auto it = forwardMap.find(in);
        if (it != forwardMap.end()) {
            return it->second;
        }
        //throw std::invalid_argument("Invalid input for forward lookup");
        return EnumType();
    }

    // Iterators for Simple Type -> Enum Type (forwardMap)
    auto getIteratorSimpleTypeBegin() { return forwardMap.begin(); }
    auto getIteratorSimpleTypeEnd() { return forwardMap.end(); }

    auto getIteratorSimpleTypeBegin() const { return forwardMap.cbegin(); }
    auto getIteratorSimpleTypeEnd() const { return forwardMap.cend(); }

    // Iterators for Enum Type -> Simple Type (reverseMap)
    auto getIteratorEnumTypeBegin() { return reverseMap.begin(); }
    auto getIteratorEnumTypeEnd() { return reverseMap.end(); }

    auto getIteratorEnumTypeBegin() const { return reverseMap.cbegin(); }
    auto getIteratorEnumTypeEnd() const { return reverseMap.cend(); }

};

enum struct ToolKind
{
    Select,
    Orbit,
    Box,
    Cylinder,
    Sphere,
    Cone,
    Torus,
    Wedge,
    Prism,
    Revolve,
    Line,
    Polyline,
    Arc,
    Circle,
    Ellipse,
    Rectangle,
    Spline
};
enum class subset { p, q, r };
enum class colors { red, blue, green, yellow };

// --- Sample Enum for Testing ---
enum class Cars {
    Toyota,
    Honda,
    Mercedes,
    Unknown // Acting as our "lastEnumerator"
};

int main() {
    ToolKind uu = ToolKind::Prism;
    EnumLookup<std::string, ToolKind, ToolKind::Spline> man1({
        {ToolKind::Prism, "Prism"},
        {ToolKind::Select, "Select"}
        });
    std::cout << "ToolKind is: " << man1.toSimpleType(uu) << std::endl;
    std::cout << "Forward Lookup - value is: " << man1.toSimpleType(man1.toEnumType("Select")) << std::endl;
    subset oo = subset::r;
    EnumLookup<char, subset, subset::r> man2({
        {subset::q, 'q'},
        {subset::r, 'r'}
        });
    std::cout << "Subset is: " << man2.toSimpleType(oo) << std::endl;
    std::cout << "Forward Lookup - value is: " << man2.toSimpleType(man2.toEnumType('q')) << std::endl;
    // Demonstrating the Deque Constructor
    colors c = colors::green;
    // Constructor Call
    EnumLookup<std::string, colors, colors::yellow> man3 (std::deque<std::string> {"red", "blue", "green", "yellow"}) ;
    std::cout << "Colors is: " << man3.toSimpleType(c) << std::endl;
    std::cout << "Forward Lookup - value is: " << man3.toSimpleType(man3.toEnumType("red")) << std::endl;



    // 1. Initialize the EnumLookup class using an initializer list (SimpleType -> EnumType)
    std::cout << "Initializing EnumLookup instance..." << std::endl;
    EnumLookup<std::string, Cars, Cars::Unknown> colorLookup({
        {"Toyota",   Cars::Toyota},
        {"Honda", Cars::Honda},
        {"Mercedes",  Cars::Mercedes}
    });
    std::cout << "Initialization complete.\n" << std::endl;

    // -------------------------------------------------------------------------
    // Test 1: Testing getIteratorSimpleType (forwardMap: string -> Color)
    // -------------------------------------------------------------------------
    std::cout << "=== Testing SimpleType Iterator (forwardMap) ===" << std::endl;
    
    auto simpleStart = colorLookup.getIteratorSimpleTypeBegin();
    auto simpleEnd   = colorLookup.getIteratorSimpleTypeEnd();

    if (simpleStart == simpleEnd) {
        std::cout << "Error: SimpleType iterator is empty!" << std::endl;
    } else {
        std::cout << "Looping through forwardMap using traditional iterators:" << std::endl;
        for (auto it = simpleStart; it != simpleEnd; ++it) {
            // it->first is the std::string (Key)
            // it->second is the Color enum (Value)
            std::cout << "  Key (string): " << it->first 
                      << " -> Value (enum as int): " << static_cast<int>(it->second) 
                      << std::endl;
        }
    }
    std::cout << "================================================\n" << std::endl;


    // -------------------------------------------------------------------------
    // Test 2: Testing getIteratorEnumType (reverseMap: Color -> string)
    // -------------------------------------------------------------------------
    std::cout << "=== Testing EnumType Iterator (reverseMap) ===" << std::endl;

    auto enumStart = colorLookup.getIteratorEnumTypeBegin();
    auto enumEnd   = colorLookup.getIteratorEnumTypeEnd();

    if (enumStart == enumEnd) {
        std::cout << "Error: EnumType iterator is empty!" << std::endl;
    } else {
        std::cout << "Looping through reverseMap using C++17 structured bindings:" << std::endl;
        for (auto it = enumStart; it != enumEnd; ++it) {
            // Deconstructing the iterator pair safely
            const auto& [enumKey, stringValue] = *it;
            
            std::cout << "  Key (enum as int): " << static_cast<int>(enumKey) 
                      << " -> Value (string): " << stringValue 
                      << std::endl;
        }
    }
    std::cout << "================================================" << std::endl;

    return 0;
}

 

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:

  • cppreference — Enumeration types https://en.cppreference.com/w/cpp/language/enum Hyperlink – CPP Reference – Enumeration, Date Accessed: 15-June-2026
  • ISO C++ Guidelines — Enum rules https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#enum-enumerations Hyperlink – CPP Core Guidelines – Enum rules, Date Accessed: 15-June-2026
  • Niklaus Wirth – Pascal (Innovation in Computer Programming), 1968-1972 http://pascal.hansotten.com/niklaus-wirth/recollections-about-the-development-of-pascal/ Hyperlink – (Non-HTTPS) link to ‘Niklaus Wirth’ Recollections, Relevant Quote: “The primary innovation of Pascal was to incorporate a variety of data types and data structures, similar to Algol’s introduction of a variety of statement structures. Algol offered only three basic data types, namely integers, real numbers, and truth values, and the array structure; Pascal introduced additional basic types and the possibility to define new basic types (enumerations, subranges), as well as new forms of structuring: record, set, and file (sequence), several of which had been present in COBOL Most important was of course the recursivity of structural definitions, and the consequent possibility to combine and nest structures freely. Along with programmer-defined data types came the clear distinction between type definition and variable declaration, variables being instances of a type.”, Date Accessed: 15-June-2026, Alternate Hyperlink: https://web.archive.org/web/20260217210113/http://pascal.hansotten.com/niklaus-wirth/recollections-about-the-development-of-pascal/ Date Accessed: 16-June-2026
  • C Language Introduction of enum, 1973-1980, ACM Article on History of C language https://dl.acm.org/doi/epdf/10.1145/234286.1057834 Hyperlink – Dennis Ritchie’s Article published 1996, Relevant Quote: “During 1973-1980, the language grew a bit: the type structure gained unsigned, long, union, and enumeration types, and structures became nearly first-class objects (lacking only a notation for literals). Equally important developments appeared in its environment and the accompanying technology.”, Date Accessed: 15-June-2026.
    Dennis M. Ritchie. 1996. The development of the C programming language. History of programming languages—II. Association for Computing Machinery, New York, NY, USA, 671–698.
  • Timeline of Programming Languages https://en.wikipedia.org/wiki/Timeline_of_programming_languages Hyperlink – Wikipedia – Timeline, Date Accessed: 15-June-2026

Leave a Reply

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