Iterating over non-incremental Enum

前端 未结 15 1967
长发绾君心
长发绾君心 2021-01-31 15:42

Before you ask, I\'ve looked and looked for this on SO, and cannot find a solid answer.

I need to be able to dynamically iterate over an enum that has non-incre

15条回答
  •  渐次进展
    2021-01-31 16:22

    The answer is "no, you cannot iterate over the elements of an enum in C++03 or C++11".

    Now, you can describe the set of values of an enum in a way that can be understood at compile time.

    template
    struct TypedEnumList {};
    
    typedef TypedEnumList<
      CAPI_SUBTYPE_E,
      CAPI_SUBTYPE_NULL, // etc
      // ...
      CAPI_SUBTYPE_DIAG_ASG
    > CAPI_SUBTYPE_E_LIST;
    

    which gives you a type CAPI_SUBTYPE_E_LIST which encapsulates the list of enum values.

    We can then populate an array with these easily:

     template
     std::array GetRuntimeArray( TypedEnumList ) {
       return { Es... };
     }
     auto Capis = GetRuntimeArray( CAPI_SUBTYPE_E_LIST() );
    

    if you really need it. But this is just a special case of the more general case of being able to generate code for each element of your enum CAPI_SUBTYPE_E -- directly building a for loop isn't needed.

    Amusingly, with a compliant C++11 compiler, we could write code that would generate our CAPI_SUBTYPE_E_LIST with particular enum elements iff those elements are actually in CAPI_SUBTYPE_E using SFINAE. This would be useful because we can take the most recent version of the API we can support, and have it auto-degrade (at compile time) if the API we compile against is more primitive.

    To demonstrate the technique, I'll start with a toy enum

    enum Foo { A = 0, /* B = 1 */ };
    

    Imagine that B=1 is uncommented in the most modern version of the API, but is not there in the more primitive.

    template
    struct AddElementN: AddElementN {};
    template
    struct AddElementN<-1, EnumList, void> {
      typedef EnumList type;
    };
    
    template
    struct AddElementN<0, TypedEnumList, typename std::enable_if< Enum::A == Enum::A >::type >:
      AddElement<-1, TypedEnumList>
    {};
    template
    struct AddElementN<1, TypedEnumList, typename std::enable_if< Enum::B == Enum::B >::type >:
      AddElement<0, TypedEnumList>
    {};
    // specialize this for your enum to call AddElementN:
    template
    struct BuildTypedList;
    template<>
    struct BuildTypedList:
      AddElementN<1, TypedEnumList>
    {};
    template
    using TypedList = typename BuildTypedList::type;
    

    now, if I wrote that right, TypedList contains B iff B is defined as an element of CAPI_SUBTYPE_E. This lets you compile against more than one version of the library, and get a different set of elements in your enum element list depending on what is in the library. You do have to maintain that annoying boilerplate (which could probably be made easier with macros or code generation) against the "final" version of the enums elements, but it should automatically handle previous versions at compile time.

    This sadly requires lots of maintenance to work.

    Finally, your requirement that this be dynamic: the only practical way for this to be dynamic is to wrap the 3rd party API in code that knows what the version of the API is, and exposes a different buffer of enum values (I'd put it in a std::vector) depending on what the version the API is. Then when you load the API, you also load this helper wrapper, which then uses the above techniques to build the set of elements of the enum, which you iterate over.

    Some of this boilerplate can be made easier to write with some horrible macros, like ones that build the various AddElementN type SFINAE code by using the __LINE__ to index the recursive types. But that would be horrible.

提交回复
热议问题