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

后端 未结 28 1936
逝去的感伤
逝去的感伤 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:19

    (Analogue of https://stackoverflow.com/a/54967187/2338477, slightly modified).

    Here is my own solution with minimum define magic and support of individual enum assignments.

    Here is header file:

    #pragma once
    #include <string>
    #include <map>
    #include <regex>
    
    template <class Enum>
    class EnumReflect
    {
    public:
        static const char* getEnums() { return ""; }
    };
    
    //
    //  Just a container for each enumeration type.
    //
    template <class Enum>
    class EnumReflectBase
    {
    public:
        static std::map<std::string, int> enum2int;
        static std::map<int, std::string> int2enum;
    
        static void EnsureEnumMapReady( const char* enumsInfo )
        {
            if (*enumsInfo == 0 || enum2int.size() != 0 )
                return;
    
            // Should be called once per each enumeration.
            std::string senumsInfo(enumsInfo);
            std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *");     // C++ identifier to optional " = <value>"
            std::smatch sm;
            int value = 0;
    
            for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
            {
                string enumName = sm[1].str();
                string enumValue = sm[2].str();
    
                if (enumValue.length() != 0)
                    value = atoi(enumValue.c_str());
    
                enum2int[enumName] = value;
                int2enum[value] = enumName;
            }
        }
    };
    
    template <class Enum>
    std::map<std::string, int> EnumReflectBase<Enum>::enum2int;
    
    template <class Enum>
    std::map<int, std::string> EnumReflectBase<Enum>::int2enum;
    
    
    #define DECLARE_ENUM(name, ...)                                         \
        enum name { __VA_ARGS__ };                                          \
        template <>                                                         \
        class EnumReflect<##name>: public EnumReflectBase<##name> {         \
        public:                                                             \
            static const char* getEnums() { return #__VA_ARGS__; }          \
        };
    
    
    
    
    /*
        Basic usage:
    
        Declare enumeration:
    
    DECLARE_ENUM( enumName,
    
        enumValue1,
        enumValue2,
        enumValue3 = 5,
    
        // comment
        enumValue4
    );
    
        Conversion logic:
    
        From enumeration to string:
    
            printf( EnumToString(enumValue3).c_str() );
    
        From string to enumeration:
    
           enumName value;
    
           if( !StringToEnum("enumValue4", value) )
                printf("Conversion failed...");
    */
    
    //
    //  Converts enumeration to string, if not found - empty string is returned.
    //
    template <class T>
    std::string EnumToString(T t)
    {
        EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
        auto& int2enum = EnumReflect<T>::int2enum;
        auto it = int2enum.find(t);
    
        if (it == int2enum.end())
            return "";
    
        return it->second;
    }
    
    //
    //  Converts string to enumeration, if not found - false is returned.
    //
    template <class T>
    bool StringToEnum(const char* enumName, T& t)
    {
        EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
        auto& enum2int = EnumReflect<T>::enum2int;
        auto it = enum2int.find(enumName);
    
        if (it == enum2int.end())
            return false;
    
        t = (T) it->second;
        return true;
    }
    

    And here is example test application:

    DECLARE_ENUM(TestEnum,
        ValueOne,
        ValueTwo,
        ValueThree = 5,
        ValueFour = 7
    );
    
    DECLARE_ENUM(TestEnum2,
        ValueOne2 = -1,
        ValueTwo2,
        ValueThree2 = -4,
        ValueFour2
    );
    
    void main(void)
    {
        string sName1 = EnumToString(ValueOne);
        string sName2 = EnumToString(ValueTwo);
        string sName3 = EnumToString(ValueThree);
        string sName4 = EnumToString(ValueFour);
    
        TestEnum t1, t2, t3, t4, t5 = ValueOne;
        bool b1 = StringToEnum(sName1.c_str(), t1);
        bool b2 = StringToEnum(sName2.c_str(), t2);
        bool b3 = StringToEnum(sName3.c_str(), t3);
        bool b4 = StringToEnum(sName4.c_str(), t4);
        bool b5 = StringToEnum("Unknown", t5);
    
        string sName2_1 = EnumToString(ValueOne2);
        string sName2_2 = EnumToString(ValueTwo2);
        string sName2_3 = EnumToString(ValueThree2);
        string sName2_4 = EnumToString(ValueFour2);
    
        TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
        bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
        bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
        bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
        bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
        bool b2_5 = StringToEnum("Unknown", t2_5);
    

    Updated version of same header file will be kept here:

    https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h

    0 讨论(0)
  • 2020-11-22 17:20

    If your enum looks like

    enum MyEnum
    {
      AAA = -8,
      BBB = '8',
      CCC = AAA + BBB
    };
    

    You can move the content of the enum to a new file:

    AAA = -8,
    BBB = '8',
    CCC = AAA + BBB
    

    And then the values can be surrounded by a macro:

    // default definition
    #ifned ITEM(X,Y)
    #define ITEM(X,Y)
    #endif
    
    // Items list
    ITEM(AAA,-8)
    ITEM(BBB,'8')
    ITEM(CCC,AAA+BBB)
    
    // clean up
    #undef ITEM
    

    Next step may be include the items in the enum again:

    enum MyEnum
    {
      #define ITEM(X,Y) X=Y,
      #include "enum_definition_file"
    };
    

    And finally you can generate utility functions about this enum:

    std::string ToString(MyEnum value)
    {
      switch( value )
      {
        #define ITEM(X,Y) case X: return #X;
        #include "enum_definition_file"
      }
    
      return "";
    }
    
    MyEnum FromString(std::string const& value)
    {
      static std::map<std::string,MyEnum> converter
      {
        #define ITEM(X,Y) { #X, X },
        #include "enum_definition_file"
      };
    
      auto it = converter.find(value);
      if( it != converter.end() )
        return it->second;
      else
        throw std::runtime_error("Value is missing");
    }
    

    The solution can be applied to older C++ standards and it does not use modern C++ elements but it can be used to generate lot of code without too much effort and maintenance.

    0 讨论(0)
  • 2020-11-22 17:21

    EDIT: check below for a newer version

    As mentioned above, N4113 is the final solution to this matter, but we'll have to wait more than a year to see it coming out.

    Meanwhile, if you want such feature, you'll need to resort to "simple" templates and some preprocessor magic.

    Enumerator

    template<typename T>
    class Enum final
    {
        const char* m_name;
        const T m_value;
        static T m_counter;
    
    public:
        Enum(const char* str, T init = m_counter) : m_name(str), m_value(init) {m_counter = (init + 1);}
    
        const T value() const {return m_value;}
        const char* name() const {return m_name;}
    };
    
    template<typename T>
    T Enum<T>::m_counter = 0;
    
    #define ENUM_TYPE(x)      using Enum = Enum<x>;
    #define ENUM_DECL(x,...)  x(#x,##__VA_ARGS__)
    #define ENUM(...)         const Enum ENUM_DECL(__VA_ARGS__);
    

    Usage

    #include <iostream>
    
    //the initialization order should be correct in all scenarios
    namespace Level
    {
        ENUM_TYPE(std::uint8)
        ENUM(OFF)
        ENUM(SEVERE)
        ENUM(WARNING)
        ENUM(INFO, 10)
        ENUM(DEBUG)
        ENUM(ALL)
    }
    
    namespace Example
    {
        ENUM_TYPE(long)
        ENUM(A)
        ENUM(B)
        ENUM(C, 20)
        ENUM(D)
        ENUM(E)
        ENUM(F)
    }
    
    int main(int argc, char** argv)
    {
        Level::Enum lvl = Level::WARNING;
        Example::Enum ex = Example::C;
        std::cout << lvl.value() << std::endl; //2
        std::cout << ex.value() << std::endl; //20
    }
    

    Simple explaination

    Enum<T>::m_counter is set to 0 inside each namespace declaration.
    (Could someone point me out where ^^this behaviour^^ is mentioned on the standard?)
    The preprocessor magic automates the declaration of enumerators.

    Disadvantages

    • It's not a true enum type, therefore not promotable to int
    • Cannot be used in switch cases

    Alternative solution

    This one sacrifices line numbering (not really) but can be used on switch cases.

    #define ENUM_TYPE(x) using type = Enum<x>
    #define ENUM(x)      constexpr type x{__LINE__,#x}
    
    template<typename T>
    struct Enum final
    {
        const T value;
        const char* name;
    
        constexpr operator const T() const noexcept {return value;}
        constexpr const char* operator&() const noexcept {return name;}
    };
    

    Errata

    #line 0 conflicts with -pedantic on GCC and clang.

    Workaround

    Either start at #line 1 and subtract 1 from __LINE__.
    Or, don't use -pedantic.
    And while we're at it, avoid VC++ at all costs, it has always been a joke of a compiler.

    Usage

    #include <iostream>
    
    namespace Level
    {
        ENUM_TYPE(short);
        #line 0
        ENUM(OFF);
        ENUM(SEVERE);
        ENUM(WARNING);
        #line 10
        ENUM(INFO);
        ENUM(DEBUG);
        ENUM(ALL);
        #line <next line number> //restore the line numbering
    };
    
    int main(int argc, char** argv)
    {
        std::cout << Level::OFF << std::endl;   // 0
        std::cout << &Level::OFF << std::endl;  // OFF
    
        std::cout << Level::INFO << std::endl;  // 10
        std::cout << &Level::INFO << std::endl; // INFO
    
        switch(/* any integer or integer-convertible type */)
        {
        case Level::OFF:
            //...
            break;
    
        case Level::SEVERE:
            //...
            break;
    
        //...
        }
    
        return 0;
    }
    

    Real-life implementation and use

    r3dVoxel - Enum
    r3dVoxel - ELoggingLevel

    Quick Reference

    #line lineno -- cppreference.com

    0 讨论(0)
  • 2020-11-22 17:22

    My 3 cents, though this is not a complete match to what the op wants. Here is the relevant reference.

    namespace enums
    {
    
    template <typename T, T I, char ...Chars>
    struct enums : std::integral_constant<T, I>
    {
      static constexpr char const chars[sizeof...(Chars)]{Chars...};
    };
    
    template <typename T, T X, typename S, std::size_t ...I>
    constexpr auto make(std::index_sequence<I...>) noexcept
    {
      return enums<T, X, S().chars[I]...>();
    }
    
    #define ENUM(s, n) []() noexcept{\
      struct S { char const (&chars)[sizeof(s)]{s}; };\
      return enums::make<decltype(n), n, S>(\
        std::make_index_sequence<sizeof(s)>());}()
    
    #define ENUM_T(s, n)\
      static constexpr auto s ## _tmp{ENUM(#s, n)};\
      using s ## _enum_t = decltype(s ## _tmp)
    
    template <typename T, typename ...A, std::size_t N>
    inline auto map(char const (&s)[N]) noexcept
    {
      constexpr auto invalid(~T{});
    
      auto r{invalid};
    
      return
        (
          (
            invalid == r ?
              r = std::strncmp(A::chars, s, N) ? invalid : A{} :
              r
          ),
          ...
        );
    }
    
    }
    
    int main()
    {
      ENUM_T(echo, 0);
      ENUM_T(cat, 1);
      ENUM_T(ls, 2);
    
      std::cout << echo_enum_t{} << " " << echo_enum_t::chars << std::endl;
    
      std::cout << enums::map<int, echo_enum_t, cat_enum_t, ls_enum_t>("ls")) << std::endl;
    
      return 0;
    }
    

    So you generate a type, that you can convert to an integer and/or a string.

    0 讨论(0)
  • 2020-11-22 17:23

    I don't know if you're going to like this or not, I'm not pretty happy with this solution but it is a C++14 friendly approach because it is using template variables and abusing template specialization:

    enum class MyEnum : std::uint_fast8_t {
       AAA,
       BBB,
       CCC,
    };
    
    template<MyEnum> const char MyEnumName[] = "Invalid MyEnum value";
    template<> const char MyEnumName<MyEnum::AAA>[] = "AAA";
    template<> const char MyEnumName<MyEnum::BBB>[] = "BBB";
    template<> const char MyEnumName<MyEnum::CCC>[] = "CCC";
    
    int main()
    {
        // Prints "AAA"
        std::cout << MyEnumName<MyEnum::AAA> << '\n';
        // Prints "Invalid MyEnum value"
        std::cout << MyEnumName<static_cast<MyEnum>(0x12345678)> << '\n';
        // Well... in fact it prints "Invalid MyEnum value" for any value
        // different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.
    
        return 0;
    }
    

    The worst about this approach is that is a pain to maintain, but it is also a pain to maintain some of other similar aproaches, aren't they?

    Good points about this aproach:

    • Using variable tempates (C++14 feature)
    • With template specialization we can "detect" when an invalid value is used (but I'm not sure if this could be useful at all).
    • It looks neat.
    • The name lookup is done at compile time.

    Live example

    Edit

    Misterious user673679 you're right; the C++14 variable template approach doesn't handles the runtime case, it was my fault to forget it :(

    But we can still use some modern C++ features and variable template plus variadic template trickery to achieve a runtime translation from enum value to string... it is as bothersome as the others but still worth to mention.

    Let's start using a template alias to shorten the access to a enum-to-string map:

    // enum_map contains pairs of enum value and value string for each enum
    // this shortcut allows us to use enum_map<whatever>.
    template <typename ENUM>
    using enum_map = std::map<ENUM, const std::string>;
    
    // This variable template will create a map for each enum type which is
    // instantiated with.
    template <typename ENUM>
    enum_map<ENUM> enum_values{};
    

    Then, the variadic template trickery:

    template <typename ENUM>
    void initialize() {}
    
    template <typename ENUM, typename ... args>
    void initialize(const ENUM value, const char *name, args ... tail)
    {
        enum_values<ENUM>.emplace(value, name);
        initialize<ENUM>(tail ...);
    }
    

    The "best trick" here is the use of variable template for the map which contains the values and names of each enum entry; this map will be the same in each translation unit and have the same name everywhere so is pretty straightforward and neat, if we call the initialize function like this:

    initialize
    (
        MyEnum::AAA, "AAA",
        MyEnum::BBB, "BBB",
        MyEnum::CCC, "CCC"
    );
    

    We are asigning names to each MyEnum entry and can be used in runtime:

    std::cout << enum_values<MyEnum>[MyEnum::AAA] << '\n';
    

    But can be improved with SFINAE and overloading << operator:

    template<typename ENUM, class = typename std::enable_if<std::is_enum<ENUM>::value>::type>
    std::ostream &operator <<(std::ostream &o, const ENUM value)
    {
        static const std::string Unknown{std::string{typeid(ENUM).name()} + " unknown value"};
        auto found = enum_values<ENUM>.find(value);
    
        return o << (found == enum_values<ENUM>.end() ? Unknown : found->second);
    }
    

    With the correct operator << now we can use the enum this way:

    std::cout << MyEnum::AAA << '\n';
    

    This is also bothersome to maintain and can be improved, but hope you get the idea.

    Live example

    0 讨论(0)
  • 2020-11-22 17:26

    (The approach of the better_enums library)

    There is a way to do enum to string in current C++ that looks like this:

    ENUM(Channel, char, Red = 1, Green, Blue)
    
    // "Same as":
    // enum class Channel : char { Red = 1, Green, Blue };
    

    Usage:

    Channel     c = Channel::_from_string("Green");  // Channel::Green (2)
    c._to_string();                                  // string "Green"
    
    for (Channel c : Channel::_values())
        std::cout << c << std::endl;
    
    // And so on...
    

    All operations can be made constexpr. You can also implement the C++17 reflection proposal mentioned in the answer by @ecatmur.

    • There is only one macro. I believe this is the minimum possible, because preprocessor stringization (#) is the only way to convert a token to a string in current C++.
    • The macro is pretty unobtrusive – the constant declarations, including initializers, are pasted into a built-in enum declaration. This means they have the same syntax and meaning as in a built-in enum.
    • Repetition is eliminated.
    • The implementation is most natural and useful in at least C++11, due to constexpr. It can also be made to work with C++98 + __VA_ARGS__. It is definitely modern C++.

    The macro's definition is somewhat involved, so I'm answering this in several ways.

    • The bulk of this answer is an implementation that I think is suitable for the space constraints on StackOverflow.
    • There is also a CodeProject article describing the basics of the implementation in a long-form tutorial. [Should I move it here? I think it's too much for a SO answer].
    • There is a full-featured library "Better Enums" that implements the macro in a single header file. It also implements N4428 Type Property Queries, the current revision of the C++17 reflection proposal N4113. So, at least for enums declared through this macro, you can have the proposed C++17 enum reflection now, in C++11/C++14.

    It is straightforward to extend this answer to the features of the library – nothing "important" is left out here. It is, however, quite tedious, and there are compiler portability concerns.

    Disclaimer: I am the author of both the CodeProject article and the library.

    You can try the code in this answer, the library, and the implementation of N4428 live online in Wandbox. The library documentation also contains an overview of how to use it as N4428, which explains the enums portion of that proposal.


    Explanation

    The code below implements conversions between enums and strings. However, it can be extended to do other things as well, such as iteration. This answer wraps an enum in a struct. You can also generate a traits struct alongside an enum instead.

    The strategy is to generate something like this:

    struct Channel {
        enum _enum : char { __VA_ARGS__ };
        constexpr static const Channel          _values[] = { __VA_ARGS__ };
        constexpr static const char * const     _names[] = { #__VA_ARGS__ };
    
        static const char* _to_string(Channel v) { /* easy */ }
        constexpr static Channel _from_string(const char *s) { /* easy */ }
    };
    

    The problems are:

    1. We will end up with something like {Red = 1, Green, Blue} as the initializer for the values array. This is not valid C++, because Red is not an assignable expression. This is solved by casting each constant to a type T that has an assignment operator, but will drop the assignment: {(T)Red = 1, (T)Green, (T)Blue}.
    2. Similarly, we will end up with {"Red = 1", "Green", "Blue"} as the initializer for the names array. We will need to trim off the " = 1". I am not aware of a great way to do this at compile time, so we will defer this to run time. As a result, _to_string won't be constexpr, but _from_string can still be constexpr, because we can treat whitespace and equals signs as terminators when comparing with untrimmed strings.
    3. Both the above need a "mapping" macro that can apply another macro to each element in __VA_ARGS__. This is pretty standard. This answer includes a simple version that can handle up to 8 elements.
    4. If the macro is to be truly self-contained, it needs to declare no static data that requires a separate definition. In practice, this means arrays need special treatment. There are two possible solutions: constexpr (or just const) arrays at namespace scope, or regular arrays in non-constexpr static inline functions. The code in this answer is for C++11 and takes the former approach. The CodeProject article is for C++98 and takes the latter.

    Code

    #include <cstddef>      // For size_t.
    #include <cstring>      // For strcspn, strncpy.
    #include <stdexcept>    // For runtime_error.
    
    
    
    // A "typical" mapping macro. MAP(macro, a, b, c, ...) expands to
    // macro(a) macro(b) macro(c) ...
    // The helper macro COUNT(a, b, c, ...) expands to the number of
    // arguments, and IDENTITY(x) is needed to control the order of
    // expansion of __VA_ARGS__ on Visual C++ compilers.
    #define MAP(macro, ...) \
        IDENTITY( \
            APPLY(CHOOSE_MAP_START, COUNT(__VA_ARGS__)) \
                (macro, __VA_ARGS__))
    
    #define CHOOSE_MAP_START(count) MAP ## count
    
    #define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))
    
    #define IDENTITY(x) x
    
    #define MAP1(m, x)      m(x)
    #define MAP2(m, x, ...) m(x) IDENTITY(MAP1(m, __VA_ARGS__))
    #define MAP3(m, x, ...) m(x) IDENTITY(MAP2(m, __VA_ARGS__))
    #define MAP4(m, x, ...) m(x) IDENTITY(MAP3(m, __VA_ARGS__))
    #define MAP5(m, x, ...) m(x) IDENTITY(MAP4(m, __VA_ARGS__))
    #define MAP6(m, x, ...) m(x) IDENTITY(MAP5(m, __VA_ARGS__))
    #define MAP7(m, x, ...) m(x) IDENTITY(MAP6(m, __VA_ARGS__))
    #define MAP8(m, x, ...) m(x) IDENTITY(MAP7(m, __VA_ARGS__))
    
    #define EVALUATE_COUNT(_1, _2, _3, _4, _5, _6, _7, _8, count, ...) \
        count
    
    #define COUNT(...) \
        IDENTITY(EVALUATE_COUNT(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1))
    
    
    
    // The type "T" mentioned above that drops assignment operations.
    template <typename U>
    struct ignore_assign {
        constexpr explicit ignore_assign(U value) : _value(value) { }
        constexpr operator U() const { return _value; }
    
        constexpr const ignore_assign& operator =(int dummy) const
            { return *this; }
    
        U   _value;
    };
    
    
    
    // Prepends "(ignore_assign<_underlying>)" to each argument.
    #define IGNORE_ASSIGN_SINGLE(e) (ignore_assign<_underlying>)e,
    #define IGNORE_ASSIGN(...) \
        IDENTITY(MAP(IGNORE_ASSIGN_SINGLE, __VA_ARGS__))
    
    // Stringizes each argument.
    #define STRINGIZE_SINGLE(e) #e,
    #define STRINGIZE(...) IDENTITY(MAP(STRINGIZE_SINGLE, __VA_ARGS__))
    
    
    
    // Some helpers needed for _from_string.
    constexpr const char    terminators[] = " =\t\r\n";
    
    // The size of terminators includes the implicit '\0'.
    constexpr bool is_terminator(char c, size_t index = 0)
    {
        return
            index >= sizeof(terminators) ? false :
            c == terminators[index] ? true :
            is_terminator(c, index + 1);
    }
    
    constexpr bool matches_untrimmed(const char *untrimmed, const char *s,
                                     size_t index = 0)
    {
        return
            is_terminator(untrimmed[index]) ? s[index] == '\0' :
            s[index] != untrimmed[index] ? false :
            matches_untrimmed(untrimmed, s, index + 1);
    }
    
    
    
    // The macro proper.
    //
    // There are several "simplifications" in this implementation, for the
    // sake of brevity. First, we have only one viable option for declaring
    // constexpr arrays: at namespace scope. This probably should be done
    // two namespaces deep: one namespace that is likely to be unique for
    // our little enum "library", then inside it a namespace whose name is
    // based on the name of the enum to avoid collisions with other enums.
    // I am using only one level of nesting.
    //
    // Declaring constexpr arrays inside the struct is not viable because
    // they will need out-of-line definitions, which will result in
    // duplicate symbols when linking. This can be solved with weak
    // symbols, but that is compiler- and system-specific. It is not
    // possible to declare constexpr arrays as static variables in
    // constexpr functions due to the restrictions on such functions.
    //
    // Note that this prevents the use of this macro anywhere except at
    // namespace scope. Ironically, the C++98 version of this, which can
    // declare static arrays inside static member functions, is actually
    // more flexible in this regard. It is shown in the CodeProject
    // article.
    //
    // Second, for compilation performance reasons, it is best to separate
    // the macro into a "parametric" portion, and the portion that depends
    // on knowing __VA_ARGS__, and factor the former out into a template.
    //
    // Third, this code uses a default parameter in _from_string that may
    // be better not exposed in the public interface.
    
    #define ENUM(EnumName, Underlying, ...)                               \
    namespace data_ ## EnumName {                                         \
        using _underlying = Underlying;                                   \
        enum { __VA_ARGS__ };                                             \
                                                                          \
        constexpr const size_t           _size =                          \
            IDENTITY(COUNT(__VA_ARGS__));                                 \
                                                                          \
        constexpr const _underlying      _values[] =                      \
            { IDENTITY(IGNORE_ASSIGN(__VA_ARGS__)) };                     \
                                                                          \
        constexpr const char * const     _raw_names[] =                   \
            { IDENTITY(STRINGIZE(__VA_ARGS__)) };                         \
    }                                                                     \
                                                                          \
    struct EnumName {                                                     \
        using _underlying = Underlying;                                   \
        enum _enum : _underlying { __VA_ARGS__ };                         \
                                                                          \
        const char * _to_string() const                                   \
        {                                                                 \
            for (size_t index = 0; index < data_ ## EnumName::_size;      \
                 ++index) {                                               \
                                                                          \
                if (data_ ## EnumName::_values[index] == _value)          \
                    return _trimmed_names()[index];                       \
            }                                                             \
                                                                          \
            throw std::runtime_error("invalid value");                    \
        }                                                                 \
                                                                          \
        constexpr static EnumName _from_string(const char *s,             \
                                               size_t index = 0)          \
        {                                                                 \
            return                                                        \
                index >= data_ ## EnumName::_size ?                       \
                        throw std::runtime_error("invalid identifier") :  \
                matches_untrimmed(                                        \
                    data_ ## EnumName::_raw_names[index], s) ?            \
                        (EnumName)(_enum)data_ ## EnumName::_values[      \
                                                                index] :  \
                _from_string(s, index + 1);                               \
        }                                                                 \
                                                                          \
        EnumName() = delete;                                              \
        constexpr EnumName(_enum value) : _value(value) { }               \
        constexpr operator _enum() const { return (_enum)_value; }        \
                                                                          \
      private:                                                            \
        _underlying     _value;                                           \
                                                                          \
        static const char * const * _trimmed_names()                      \
        {                                                                 \
            static char     *the_names[data_ ## EnumName::_size];         \
            static bool     initialized = false;                          \
                                                                          \
            if (!initialized) {                                           \
                for (size_t index = 0; index < data_ ## EnumName::_size;  \
                     ++index) {                                           \
                                                                          \
                    size_t  length =                                      \
                        std::strcspn(data_ ## EnumName::_raw_names[index],\
                                     terminators);                        \
                                                                          \
                    the_names[index] = new char[length + 1];              \
                                                                          \
                    std::strncpy(the_names[index],                        \
                                 data_ ## EnumName::_raw_names[index],    \
                                 length);                                 \
                    the_names[index][length] = '\0';                      \
                }                                                         \
                                                                          \
                initialized = true;                                       \
            }                                                             \
                                                                          \
            return the_names;                                             \
        }                                                                 \
    };
    

    and

    // The code above was a "header file". This is a program that uses it.
    #include <iostream>
    #include "the_file_above.h"
    
    ENUM(Channel, char, Red = 1, Green, Blue)
    
    constexpr Channel   channel = Channel::_from_string("Red");
    
    int main()
    {
        std::cout << channel._to_string() << std::endl;
    
        switch (channel) {
            case Channel::Red:   return 0;
            case Channel::Green: return 1;
            case Channel::Blue:  return 2;
        }
    }
    
    static_assert(sizeof(Channel) == sizeof(char), "");
    

    The program above prints Red, as you would expect. There is a degree of type safety, since you can't create an enum without initializing it, and deleting one of the cases from the switch will result in a warning from the compiler (depending on your compiler and flags). Also, note that "Red" was converted to an enum during compilation.

    0 讨论(0)
提交回复
热议问题