Searchable Enum-like object with string and int conversion

后端 未结 2 1435
感动是毒
感动是毒 2020-12-29 05:36

Intro

The enum type in C++ is fairly basic; it basically just creates a bunch of compile-time values for labels (potentially with proper scoping with

相关标签:
2条回答
  • 2020-12-29 05:41

    I usually prefer non-macro code but in this case, I don't see what's wrong with macros.
    IMHO, for this task macros are a much better fit as they are simpler and shorter to write and to read, and the same goes for the generated code. Simplicity is a goal in its own right.

    These 2 macro calls:

    #define Animal_Members(LAMBDA) \
        LAMBDA(DOG) \
        LAMBDA(CAT) \
        LAMBDA(COW) \
    
    CREATE_ENUM(Animal,None);
    

    Generate this:

    struct Animal {
      enum Id {
        None,
        DOG,
        CAT,
        COW
      };
      static Id fromString( const char* s ) {
        if( !s ) return None;
        if( strcmp(s,"DOG")==0 ) return DOG;
        if( strcmp(s,"CAT")==0 ) return CAT;
        if( strcmp(s,"COW")==0 ) return COW;
        return None;
      }
      static const char* toString( Id id ) {
        switch( id ) {
          case DOG: return "DOG";
          case CAT: return "CAT";
          case COW: return "COW";
          default: return nullptr;
        }
      }
      static size_t count() {
        static Id all[] = { None, DOG, CAT, COW };
        return sizeof(all) / sizeof(Id);
      }
    };
    

    You could wrap them into a single macro using BOOST_PP and have a sequence for the members. This would make it a lot less readable, though.
    You can easily change it to your preferences of default return values, or remove the default altogether, add a specific member value and string name, etc.
    There's no loose functions, no init order hell, and only a bit of macro code that looks very much like the final result:

    #define ENUM_MEMBER(MEMBER)                         \
        , MEMBER
    #define ENUM_FROM_STRING(MEMBER)                    \
        if( strcmp(s,#MEMBER)==0 ) return MEMBER;
    #define ENUM_TO_STRING(MEMBER)                      \
        case MEMBER: return #MEMBER;
    #define CREATE_ENUM_1(NAME,MACRO,DEFAULT)           \
        struct NAME {                                   \
            enum Id {                                   \
                DEFAULT                                 \
                MACRO(ENUM_MEMBER)                      \
            };                                          \
            static Id fromString( const char* s ) {     \
                if( !s ) return DEFAULT;                \
                MACRO(ENUM_FROM_STRING)                 \
                return DEFAULT;                         \
            }                                           \
            static const char* toString( Id id ) {      \
                switch( id ) {                          \
                MACRO(ENUM_TO_STRING)                   \
                default: return nullptr;                \
                }                                       \
            }                                           \
            static size_t count() {                     \
                static Id all[] = { DEFAULT             \
                    MACRO(ENUM_MEMBER) };               \
                return sizeof(all) / sizeof(Id);        \
            }                                           \
        };
    #define CREATE_ENUM_2(NAME,DEFAULT) \
        CREATE_ENUM_1(NAME,NAME##_Members,DEFAULT)
    #define CREATE_ENUM(NAME,DEFAULT) \
        CREATE_ENUM_2(NAME,DEFAULT)
    

    Hope this helps.

    0 讨论(0)
  • 2020-12-29 06:04

    Sometimes when you want to do something that isn't supported by the language, you should look external to the language to support it. In this case, code-generation seems like the best option.

    Start with a file with your enumeration. I'll pick XML completely arbitrarily, but really any reasonable format is fine:

    <enum name="MyEnum">
        <item name="ALPHA" />
        <item name="BETA" />
        <item name="GAMMA" />
    </enum>
    

    It's easy enough to add whatever optional fields you need in there (do you need a value? Should the enum be unscoped? Have a specified type?).

    Then you write a code generator in the language of your choice that turns that file into a C++ header (or header/source) file a la:

    enum class MyEnum {
        ALPHA,
        BETA,
        GAMMA,
    };
    
    std::string to_string(MyEnum e) {
        switch (e) {
        case MyEnum::ALPHA: return "ALPHA";
        case MyEnum::BETA: return "BETA";
        case MyEnum::GAMMA: return "GAMMA";
        }
    }
    
    MyEnum to_enum(const std::string& s) {
        static std::unordered_map<std::string, MyEnum> m{
            {"ALPHA", MyEnum::ALPHA},
            ...
        };
    
        auto it = m.find(s);
        if (it != m.end()) {
            return it->second;
        }
        else {
            /* up to you */
        }
    }
    

    The advantage of the code generation approach is that it's easy to generate whatever arbitrary complex code you want for your enums. Basically just side-step all the problems you're currently having.

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