Enum to string in C++11

后端 未结 6 1816
囚心锁ツ
囚心锁ツ 2021-02-03 21:52

I realize this has been asked before more than once on SO but I couldn\'t find a question explicitly looking for a current solution to this issue with C++11, so here we go again

相关标签:
6条回答
  • 2021-02-03 22:18

    The longstanding and unnecessary lack of a generic enum-to-string feature in C++ (and C) is a painful one. C++11 didn't address this, and as far as I know neither will C++14.

    Personally I'd solve this problem using code generation. The C preprocessor is one way--you can see some other answers linked in the comments here for that. But really I prefer to just write my own code generation specifically for enums. It can then easily generate to_string (char*), from_string, ostream operator<<, istream operator<<, is_valid, and more methods as needed. This approach can be very flexible and powerful, yet it enforces absolute consistency across many enums in a project, and it incurs no runtime cost.

    Do it using Python's excellent "mako" package, or in Lua if you're into lightweight, or the CPP if you're against dependencies, or CMake's own facilities for generating code. Lots of ways, but it all comes down to the same thing: you need to generate the code yourself--C++ won't do this for you (unfortunately).

    0 讨论(0)
  • 2021-02-03 22:18

    Here is a simple example using namespaces and structs. A class is created for each enum item. In this example i chose int as the type for the id.

    #include <iostream>
    using namespace std;
    
    #define ENUMITEM(Id, Name) \
    struct Name {\
        static constexpr const int id = Id;\
        static constexpr const char* name = #Name;\
    };
    
    namespace Food {
    ENUMITEM(1, Banana)
    ENUMITEM(2, Apple)
    ENUMITEM(3, Orange)
    }
    
    int main() {
        cout << Food::Orange::id << ":" << Food::Orange::name << endl;
        return 0;
    }
    

    Output:

    3:Orange
    

    == Update ==

    Using:

    #define STARTENUM() constexpr const int enumStart = __LINE__;
    #define ENUMITEM(Name) \
    struct Name {\
        static constexpr const int id = __LINE__ - enumStart - 1;\
        static constexpr const char* name = #Name;\
    };
    

    and using it once before the first usage of ENUMITEM the ids would not be needed anymore.

    namespace Food {
    STARTENUM()
    ENUMITEM(Banana)
    ENUMITEM(Apple)
    ENUMITEM(Orange)
    }
    

    The variable enumStart is only accessible through the namespace - so still multiple enums can be used.

    0 讨论(0)
  • 2021-02-03 22:25

    In my opinion, the most maintainable approach is to write a helper function:

    const char* get_name(OS_type os) {
      switch (os) {
      case Linux: return "Linux";
      case Apple: return "Apple";
      case Windows: return "Windows";
      }
    }
    

    It is a good idea not to implement the "default" case, since doing so will ensure that you get a compiler warning if you forget to implement a case (with the right compiler and compiler settings).

    0 讨论(0)
  • 2021-02-03 22:34

    I like a hack using the C preprocessor, which I first saw here: http://blogs.msdn.com/b/vcblog/archive/2008/04/30/enums-macros-unicode-and-token-pasting.aspx .

    It uses the token-pasting operator # .

    // This code defines the enumerated values:
    
    #define MY_ENUM(x) x,
    enum Fruit_Type {
    MY_ENUM(Banana)
    MY_ENUM(Apple)
    MY_ENUM(Orange)
    };
    #undef MY_ENUM
    
    // and this code defines an array of string literals for them:
    
    #define MY_ENUM(x) #x,
            const char* const fruit_name[] = {
    MY_ENUM(Banana)
    MY_ENUM(Apple)
    MY_ENUM(Orange)
            };
    #undef MY_ENUM
    
    // Finally, here is some client code:
    
    std::cout << fruit_name[Banana] << " is enum #" << Banana << "\n";
    
    // In practice, those three "MY_ENUM" macro calls will be inside an #include file.
    

    Frankly, it's ugly and. but you end up typing your enums exactly ONCE in an include file, which is more maintainable.

    BTW, on that MSDN blog link (see above) a user made a comment with a trick that makes the whole thing much prettier, and avoids #includes:

    #define Fruits(FOO) \
    FOO(Apple) \
    FOO(Banana) \
    FOO(Orange)
    
    #define DO_DESCRIPTION(e)  #e,
    #define DO_ENUM(e)  e,
    
    char* FruitDescription[] = {
    Fruits(DO_DESCRIPTION)
    };
    
    enum Fruit_Type {
    Fruits(DO_ENUM)
    };
    
    // Client code:
    
    std::cout << FruitDescription[Banana] << " is enum #" << Banana << "\n";
    

    (I just noticed that 0x17de's answer also uses the token-pasting operator)

    0 讨论(0)
  • 2021-02-03 22:39

    You can use macro to solve this problem:

    #define MAKE_ENUM(name, ...) enum class name { __VA_ARGS__}; \
    static std::vector<std::string> Enum_##name##_init(){\
        const std::string content = #__VA_ARGS__; \
        std::vector<std::string> str;\
        size_t len = content.length();\
        std::ostringstream temp;\
        for(size_t i = 0; i < len; i ++) {\
        if(isspace(content[i])) continue;\
        else if(content[i] == ',') {\
        str.push_back(temp.str());\
        temp.str(std::string());}\
        else temp<< content[i];}\
        str.push_back(temp.str());\
        return str;}\
    static const std::vector<std::string> Enum_##name##_str_vec = Enum_##name##_init();\
    static std::string to_string(name val){\
        return Enum_##name##_str_vec[static_cast<size_t>(val)];\
    }\
    static std::string print_all_##name##_enum(){\
        int count = 0;\
        std::string ans;\
        for(auto& item:Enum_##name##_str_vec)\
        ans += std::to_string(count++) + ':' + item + '\n';\
        return ans;\
    }
    

    As the static variable can only be initialized once, so the Enum_##name##_str_vec will use the Enum_##name##_init() function to initialize itself at first.

    The sample code is as below:

    MAKE_ENUM(Analysis_Time_Type,
                      UNKNOWN,
                      REAL_TIME, 
                      CLOSSING_TIME 
    );
    

    Then you can use below sentence to print an enum value:

    to_string(Analysis_Time_Type::UNKNOWN)
    

    And use below sentence to print all enum as string:

    print_all_Analysis_Time_Type_enum()
    
    0 讨论(0)
  • 2021-02-03 22:40

    As mentioned, there is no standard way to do this. But with a little preprocessor magic (similar to AlejoHausner's second contribution) and some template magic, it can be fairly elegant.

    Include this code once:

    #include <string>
    #include <algorithm>
    
    #define ENUM_VALS( name ) name,
    #define ENUM_STRINGS( name ) # name,
    
    /** Template function to return the enum value for a given string
    *  Note: assumes enums are all upper or all lowercase,
    *        that they are contiguous/default-ordered,
    *        and that the first value is the default
    *  @tparam ENUM     type of the enum to retrieve
    *  @tparam ENUMSIZE number of elements in the enum (implicit; need not be passed in)
    *  @param valStr   string version of enum value to convert; may be any capitalization (capitalization may be modified)
    *  @param enumStrs array of strings corresponding to enum values, assumed to all be in lower/upper case depending upon
    *  enumsUpper
    *  @param enumsUpper true if the enum values are in all uppercase, false if in all lowercase (mixed case not supported)
    *  @return enum value corresponding to valStr, or the first enum value if not found
    */
    template <typename ENUM, size_t ENUMSIZE>
    static inline ENUM fromString(std::string &valStr, const char *(&enumStrs)[ENUMSIZE], bool enumsUpper = true) {
        ENUM e = static_cast< ENUM >(0); // by default, first value
        // convert valStr to lower/upper-case
        std::transform(valStr.begin(), valStr.end(), valStr.begin(), enumsUpper ? ::toupper : ::tolower);
        for (size_t i = 0; i< ENUMSIZE; i++) {
            if (valStr == std::string(enumStrs[i])) {
                e = static_cast< ENUM >(i);
                break;
            }
        }
        return e;
    }
    

    Then define each enum like so:

    //! Define ColorType enum with array for converting to/from strings
    #define ColorTypes(ENUM) \
        ENUM(BLACK) \
        ENUM(RED) \
        ENUM(GREEN) \
        ENUM(BLUE)
    enum ColorType {
        ColorTypes(ENUM_VALS)
    };
    static const char* colorTypeNames[] = {
        ColorTypes(ENUM_STRINGS)
    };
    

    You only have to enumerate the enum values once and the code to define it is fairly compact and intuitive.

    Values will necessarily be numbered in the default way (ie, 0,1,2,...). The code of fromString() assumes that enum values are in either all uppercase or all lowercase (for converting from strings) that the default value is first, but you can of course change how these things are handled.

    Here is how you get the string value:

    ColorType c = ColorType::BLUE;   
    std::cout << colorTypeNames[c]; // BLUE
    

    Here is how you set the enum from a string value:

    ColorType c2 = fromString<ColorType>("Green", colorTypeNames); // == ColorType::GREEN
    
    0 讨论(0)
提交回复
热议问题