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

后端 未结 28 2230
逝去的感伤
逝去的感伤 2020-11-22 16:57

Contrary to all other similar questions, this question is about using the new C++ features.

  • 2008 c Is there a simple way to convert C++ enum to string?
  • <
28条回答
  •  无人及你
    2020-11-22 17:34

    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 
    #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 
    #include 
    

    Example usage:

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

    Code

    This solution requires 2 source files:

    // EnumTraits.h
    #pragma once
    #include 
    #include 
    #include 
    
    #define ETRAITS
    #define EDECL(x) x
    
    template 
    class EnumTraits
    {
    public:
        static const std::vector& 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;
        using name_map_type = std::unordered_map;
        using value_map_type = std::unordered_map;
    
    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  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;
    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
    }
    

提交回复
热议问题