Iterating over non-incremental Enum

前端 未结 15 1702
长发绾君心
长发绾君心 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:09

    I agree with the already given statements that this isn't possible without either altering or copying the definitions of the enum. However, in C++11 (maybe even C++03?) you can go as far as providing a syntax where all you have to do (literally) is to copy and paste the enumerator definitions from the enum into a macro. This works as long as every enumerator has an explicit definition (using =).

    Edit: You can expand this to work even if not every enumerator has an explicit definition, but this shouldn't be required in this case.

    I've once developed this for some physicists, so the example is about particles.

    Usage example:

    // required for this example
    #include 
    
    enum ParticleEnum
    {
        PROTON = 11,
        ELECTRON = 42,
        MUON = 43
    };
    
    // define macro (see below)
    
    MAKE_ENUM(
        ParticleEnum,                     // name of enum type
        particle_enum_detail,             // some namespace to place some types in
        all_particles,                    // name of array to list all enumerators
    
        // paste the enumerator definitions of your enum here
        PROTON = 11,
        ELECTRON = 42,
        MUON = 43
    ) // don't forget the macro's closing paranthesis
    
    int main()
    {
        for(ParticleEnum p : all_particles)
        {
            std::cout << p << ", ";
        }
    }
    

    The macro yields to (effectively):

    namespace particle_enum_detail
    {
        // definition of a type and some constants
    
        constexpr ParticleEnum all_particles[] = {
            PROTON,
            ELECTRON,
            MUON
        };
    }
    using particle_enum_detail::all_particles;
    

    macro definition

    #define MAKE_ENUM(ENUM_TYPE, NAMESPACE, ARRAY_NAME, ...)                 \
        namespace NAMESPACE                                                  \
        {                                                                    \
            struct iterable_enum_                                            \
            {                                                                \
                using storage_type = ENUM_TYPE;                              \
                template < typename T >                                      \
                constexpr iterable_enum_(T p)                                \
                    : m{ static_cast(p) }                      \
                {}                                                           \
                constexpr operator storage_type()                            \
                {  return m;  }                                              \
                template < typename T >                                      \
                constexpr iterable_enum_ operator= (T p)                     \
                {  return { static_cast(p) };  }               \
            private:                                                         \
                storage_type m;                                              \
            };                                                               \
                                                                             \
            /* the "enumeration" */                                          \
            constexpr iterable_enum_ __VA_ARGS__;                            \
            /* the array to store all "enumerators" */                       \
            constexpr ENUM_TYPE ARRAY_NAME[] = { __VA_ARGS__ };              \
        }                                                                    \
        using NAMESPACE::ARRAY_NAME;                              // macro end
    

    Note: the type iterable_enum_ could as well be defined once outside the macro.


    macro explanation

    The idea is to allow a syntax like proton = 11, electron = 12 within the macro invocation. This works very easy for any kind of declaration, yet it makes problems for storing the names:

    #define MAKE_ENUM(ASSIGNMEN1, ASSIGNMENT2) \
        enum my_enum { ASSIGNMENT1, ASSIGNMENT2 }; \
        my_enum all[] = { ASSIGNMENT1, ASSIGNMENT2 };
    MAKE_ENUM(proton = 11, electron = 22);
    

    yields to:

    enum my_enum { proton = 11, electron = 22 };    // would be OK
    my_enum all[] = { proton = 11, electron = 22 }; // cannot assign to enumerator
    

    As with many syntactical tricks, operator overloading provides a way to overcome this problem; but the assignment operator has to be a member functions - and enums are not classes. So why not use some constant objects instead of an enum?

    enum my_enum { proton = 11, electron = 22 };
    // alternatively
    constexpr int proton = 11, electron = 12;
    // the `constexpr` here is equivalent to a `const`
    

    This does not yet solve our problem, it just demonstrates we can easily replace enums by a list of constants if we don't need the auto-increment feature of enumerators.

    Now, the syntactical trick with operator overloading:

    struct iterable_enum_
    {
        // the trick: a constexpr assignment operator
        constexpr iterable_enum_ operator= (int p)             // (op)
        {  return {p};  }
    
        // we need a ctor for the syntax `object = init`
        constexpr iterable_enum_(int p)                        // (ctor)
            : m{ static_cast(p) }
        {}
    private:
        ParticleEnum m;
    };
    
    constexpr iterable_enum_ proton = 11, electron = 22;              // (1)
    iterable_enum_ all_particles[] = { proton = 11, electron = 22 };  // (2)
    

    The trick is, in line (1) the = designates a copy-initialisation, which is done by converting the number (11, 22) to a temporary of type particle by using the (ctor) and copying/moving the temporary via an implicitly-defined ctor to the destination object (proton, electron).

    In contrast, the = in line (2) is resolved to an operator call to (op), which effectively returns a copy of the object on which it has been called (*this). The constexpr stuff allows to use these variables at compile time, e.g. in a template declaration. Due to restrictions on constexpr functions, we cannot simply return *this in the (op) function. Additionally, constexpr implies all restrictions of const.

    By providing an implicit conversion operator, you can create the array in line (2) of type ParticleEnum:

    // in struct particle
    constexpr operator ParticleEnum() { return m; }
    
    // in namespace particle_enum_detail
    ParticleEnum all_particles[] = { proton = 11, electron = 22 };
    

提交回复
热议问题