[c++] enum to string in modern C++11 / C++14 / C++17 and future C++20

As long as you are okay with writing a separate .h/.cpp pair for each queryable enum, this solution works with nearly the same syntax and capabilities as a regular c++ enum:

// MyEnum.h
#include <EnumTraits.h>
#ifndef ENUM_INCLUDE_MULTI
#pragma once
#end if

enum MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = AAA + BBB
};

The .cpp file is 3 lines of boilerplate:

// MyEnum.cpp
#define ENUM_DEFINE MyEnum
#define ENUM_INCLUDE <MyEnum.h>
#include <EnumTraits.inl>

Example usage:

for (MyEnum value : EnumTraits<MyEnum>::GetValues())
    std::cout << EnumTraits<MyEnum>::GetName(value) << std::endl;

Code

This solution requires 2 source files:

// EnumTraits.h
#pragma once
#include <string>
#include <unordered_map>
#include <vector>

#define ETRAITS
#define EDECL(x) x

template <class ENUM>
class EnumTraits
{
public:
    static const std::vector<ENUM>& GetValues()
    {
        return values;
    }

    static ENUM GetValue(const char* name)
    {
        auto match = valueMap.find(name);
        return (match == valueMap.end() ? ENUM() : match->second);
    }

    static const char* GetName(ENUM value)
    {
        auto match = nameMap.find(value);
        return (match == nameMap.end() ? nullptr : match->second);
    }

public:
    EnumTraits() = delete;

    using vector_type = std::vector<ENUM>;
    using name_map_type = std::unordered_map<ENUM, const char*>;
    using value_map_type = std::unordered_map<std::string, ENUM>;

private:
    static const vector_type values;
    static const name_map_type nameMap;
    static const value_map_type valueMap;
};

struct EnumInitGuard{ constexpr const EnumInitGuard& operator=(int) const { return *this; } };
template <class T> constexpr T& operator<<=(T&& x, const EnumInitGuard&) { return x; }

...and

// EnumTraits.inl
#define ENUM_INCLUDE_MULTI

#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

using EnumType = ENUM_DEFINE;
using TraitsType = EnumTraits<EnumType>;
using VectorType = typename TraitsType::vector_type;
using NameMapType = typename TraitsType::name_map_type;
using ValueMapType = typename TraitsType::value_map_type;
using NamePairType = typename NameMapType::value_type;
using ValuePairType = typename ValueMapType::value_type;

#define ETRAITS ; const VectorType TraitsType::values
#define EDECL(x) EnumType::x <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const NameMapType TraitsType::nameMap
#define EDECL(x) NamePairType(EnumType::x, #x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const ValueMapType TraitsType::valueMap
#define EDECL(x) ValuePairType(#x, EnumType::x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

Explanation

This implementation exploits the fact that the braced list of elements of an enum definition can also be used as a braced initializer list for class member initialization.

When ETRAITS is evaluated in the context of EnumTraits.inl, it expands out to a static member definition for the EnumTraits<> class.

The EDECL macro transforms each enum member into initializer list values which subsequently get passed into the member constructor in order to populate the enum info.

The EnumInitGuard class is designed to consume the enum initializer values and then collapse - leaving a pure list of enum data.

Benefits

  • c++-like syntax
  • Works identically for both enum and enum class (*almost)
  • Works for enum types with any numeric underlying type
  • Works for enum types with automatic, explicit, and fragmented initializer values
  • Works for mass renaming (intellisense linking preserved)
  • Only 5 preprocessor symbols (3 global)

* In contrast to enums, initializers in enum class types that reference other values from the same enum must have those values fully qualified

Disbenefits

  • Requires a separate .h/.cpp pair for each queryable enum
  • Depends on convoluted macro and include magic
  • Minor syntax errors explode into much larger errors
  • Defining class or namespace scoped enums is nontrivial
  • No compile time initialization

Comments

Intellisense will complain a bit about private member access when opening up EnumTraits.inl, but since the expanded macros are actually defining class members, that isn't actually a problem.

The #ifndef ENUM_INCLUDE_MULTI block at the top of the header file is a minor annoyance that could probably be shrunken down into a macro or something, but it's small enough to live with at its current size.

Declaring a namespace scoped enum requires that the enum first be forward declared inside its namespace scope, then defined in the global namespace. Additionally, any enum initializers using values of the same enum must have those values fully qualified.

namespace ns { enum MyEnum : int; }
enum ns::MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = ns::MyEnum::AAA + ns::MyEnum::BBB
}

Examples related to c++

Method Call Chaining; returning a pointer vs a reference? How can I tell if an algorithm is efficient? Difference between opening a file in binary vs text How can compare-and-swap be used for a wait-free mutual exclusion for any shared data structure? Install Qt on Ubuntu #include errors detected in vscode Cannot open include file: 'stdio.h' - Visual Studio Community 2017 - C++ Error How to fix the error "Windows SDK version 8.1" was not found? Visual Studio 2017 errors on standard headers How do I check if a Key is pressed on C++

Examples related to string

How to split a string in two and store it in a field String method cannot be found in a main class method Kotlin - How to correctly concatenate a String Replacing a character from a certain index Remove quotes from String in Python Detect whether a Python string is a number or a letter How does String substring work in Swift How does String.Index work in Swift swift 3.0 Data to String? How to parse JSON string in Typescript

Examples related to enums

Enums in Javascript with ES6 Check if value exists in enum in TypeScript Why Python 3.6.1 throws AttributeError: module 'enum' has no attribute 'IntFlag'? TypeScript enum to object array How can I loop through enum values for display in radio buttons? How to get all values from python enum class? Get enum values as List of String in Java 8 enum to string in modern C++11 / C++14 / C++17 and future C++20 Implementing Singleton with an Enum (in Java) Swift: Convert enum value to String?

Examples related to c++17

How to enable C++17 compiling in Visual Studio? What are the new features in C++17? enum to string in modern C++11 / C++14 / C++17 and future C++20 Iterator invalidation rules What are Aggregates and PODs and how/why are they special?

Examples related to c++20

enum to string in modern C++11 / C++14 / C++17 and future C++20