Iterating over non-incremental Enum

前端 未结 15 1722
长发绾君心
长发绾君心 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<typename E, E... Es>
    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<typename T, T... Es>
     std::array<T, sizeof...(Es)> GetRuntimeArray( TypedEnumList<T, Es... > ) {
       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<int index, typename EnumList, typename=void>
    struct AddElementN: AddElementN<index-1, EnumList> {};
    template<typename EnumList>
    struct AddElementN<-1, EnumList, void> {
      typedef EnumList type;
    };
    
    template<typename Enum, Enum... Es>
    struct AddElementN<0, TypedEnumList<Enum, Es...>, typename std::enable_if< Enum::A == Enum::A >::type >:
      AddElement<-1, TypedEnumList<Enum, A, Es...>>
    {};
    template<typename Enum, Enum... Es>
    struct AddElementN<1, TypedEnumList<Enum, Es...>, typename std::enable_if< Enum::B == Enum::B >::type >:
      AddElement<0, TypedEnumList<Enum, B, Es...>>
    {};
    // specialize this for your enum to call AddElementN:
    template<typename Enum>
    struct BuildTypedList;
    template<>
    struct BuildTypedList<CAPI_SUBTYPE_E>:
      AddElementN<1, TypedEnumList<CAPI_SUBTYPE_E>>
    {};
    template<typename Enum>
    using TypedList = typename BuildTypedList<Enum>::type;
    

    now, if I wrote that right, TypedList<CAPI_SUBTYPE_E> 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.

    0 讨论(0)
  • 2021-01-31 16:22

    the beginnings of a solution involving no macros and (almost) no runtime overhead:

    #include <iostream>
    #include <utility>
    #include <boost/mpl/vector.hpp>
    #include <boost/mpl/find.hpp>
    
    template<int v> using has_value = std::integral_constant<int, v>;
    
    template<class...EnumValues>
    struct better_enum
    {
        static constexpr size_t size = sizeof...(EnumValues);
        using value_array = int[size];
        static const value_array& values() {
            static const value_array _values = { EnumValues::value... };
            return _values;
        }
        using name_array = const char*[size];
        static const name_array& names() {
            static const name_array _names = { EnumValues::name()... };
            return _names;
        }
    
    
        using enum_values = boost::mpl::vector<EnumValues...>;
    
        struct iterator {
            explicit iterator(size_t i) : index(i) {}
    
            const char* name() const {
                return names()[index];
            }
            int value() const {
                return values()[index];
            }
            operator int() const {
                return value();
            }
    
            void operator++() {
                ++index;
            }
            bool operator==(const iterator& it) const {
                return index == it.index;
            }
            bool operator!=(const iterator& it) const {
                return index != it.index;
            }
            const iterator& operator*() const {
                return *this;
            }
        private:
            size_t index;
        };
        friend std::ostream& operator<<(std::ostream& os, const iterator& iter)
        {
            os << "{ " << iter.name() << ", " << iter.value() << " }";
            return os;
        }
    
        template<class EnumValue>
        static iterator find() {
            using iter = typename boost::mpl::find<enum_values, EnumValue>::type;
            static_assert(iter::pos::value < size, "attempt to find a value which is not part of this enum");
            return iterator { iter::pos::value };
        }
    
        static iterator begin() {
            return iterator { 0 };
        }
    
        static iterator end() {
            return iterator { size };
        }
    
    };
    
    struct Pig : has_value<0> { static const char* name() { return "Pig";} };
    struct Dog : has_value<7> { static const char* name() { return "Dog";} };
    struct Cat : has_value<100> { static const char* name() { return "Cat";} };
    struct Horse : has_value<90> { static const char* name() { return "Horse";} };
    
    struct Monkey : has_value<1000> { static const char* name() { return "Monkey";} };
    
    using animals = better_enum<
    Pig,
    Dog,
    Cat,
    Horse
    >;
    
    using namespace std;
    
    auto main() -> int
    {
        cout << "size : " << animals::size << endl;
        for (auto v : animals::values())
        cout << v << endl;
    
        for (auto v : animals::names())
        cout << v << endl;
    
        cout << "full iteration:" << endl;
        for (const auto& i : animals())
        {
            cout << i << endl;
        }
    
        cout << "individials" << endl;
        auto animal = animals::find<Dog>();
        cout << "found : " << animal << endl;
        while (animal != animals::find<Horse>()) {
            cout << animal << endl;
            ++animal;
        }
    
    // will trigger the static_assert    auto xx = animals::find<Monkey>();
    
        return 0;
    }
    

    output:

    size : 4
    0
    7
    100
    90
    Pig
    Dog
    Cat
    Horse
    full iteration:
    { Pig, 0 }
    { Dog, 7 }
    { Cat, 100 }
    { Horse, 90 }
    individials
    found : { Dog, 7 }
    { Dog, 7 }
    { Cat, 100 }
    
    0 讨论(0)
  • 2021-01-31 16:24

    Put them into an array or other container and iterate over that. If you modify the enum, you will have to update the code that puts them in the container.

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