Iterating over non-incremental Enum

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

    Based on the articles given at the begin of the question, I derived a solution that is based in the assumption that you know the invalids ranges.

    I really wanna knows if this is a good solution.

    First, end you enum with something like that: CAPI_END = 60. It will helps to interates. So my code is:

    typedef enum {
        CAPI_SUBTYPE_NULL = 0,               /* Null subtype. */
        CAPI_SUBTYPE_DIAG_DFD = 1,           /* Data Flow diag. */
        CAPI_SUBTYPE_DIAG_ERD = 2,           /* Entity-Relationship diag. */
        CAPI_SUBTYPE_DIAG_STD = 3,           /* State Transition diag. */
        CAPI_SUBTYPE_DIAG_STC = 4,           /* Structure Chart diag. */
        CAPI_SUBTYPE_DIAG_DSD = 5,           /* Data Structure diag. */
        CAPI_SUBTYPE_SPEC_PROCESS = 6,       /* Process spec. */
        CAPI_SUBTYPE_SPEC_MODULE = 7,        /* Module spec. */
        CAPI_SUBTYPE_SPEC_TERMINATOR = 8,    /* Terminator spec. */
    
        CAPI_SUBTYPE_DD_ALL = 13,            /* DD Entries (All). */
        CAPI_SUBTYPE_DD_COUPLE = 14,         /* DD Entries (Couples). */
        CAPI_SUBTYPE_DD_DATA_AREA = 15,      /* DD Entries (Data Areas). */
        CAPI_SUBTYPE_DD_DATA_OBJECT = 16,    /* DD Entries (Data Objects). */
        CAPI_SUBTYPE_DD_FLOW = 17,           /* DD Entries (Flows). */
        CAPI_SUBTYPE_DD_RELATIONSHIP = 18,   /* DD Entries (Relationships). */
        CAPI_SUBTYPE_DD_STORE = 19,          /* DD Entries (Stores). */
    
        CAPI_SUBTYPE_DIAG_PAD = 35,          /* Physical architecture diagram. */
        CAPI_SUBTYPE_DIAG_BD  = 36,          /* Behaviour diagram. */
        CAPI_SUBTYPE_DIAG_UCD = 37,          /* UML Use case diagram. */
        CAPI_SUBTYPE_DIAG_PD  = 38,          /* UML Package diagram. */
        CAPI_SUBTYPE_DIAG_COD = 39,          /* UML Collaboration diagram. */
        CAPI_SUBTYPE_DIAG_SQD = 40,          /* UML Sequence diagram. */
        CAPI_SUBTYPE_DIAG_CD  = 41,          /* UML Class diagram. */
        CAPI_SUBTYPE_DIAG_SCD = 42,          /* UML State chart. */
        CAPI_SUBTYPE_DIAG_ACD = 43,          /* UML Activity chart. */
        CAPI_SUBTYPE_DIAG_CPD = 44,          /* UML Component diagram. */
        CAPI_SUBTYPE_DIAG_DPD = 45,          /* UML Deployment diagram. */
        CAPI_SUBTYPE_DIAG_PFD = 47,          /* Process flow diagram. */
        CAPI_SUBTYPE_DIAG_HIER = 48,         /* Hierarchy diagram. */
        CAPI_SUBTYPE_DIAG_IDEF0 = 49,        /* IDEF0 diagram. */
        CAPI_SUBTYPE_DIAG_AID = 50,          /* AID diagram. */
        CAPI_SUBTYPE_DIAG_SAD = 51,          /* SAD diagram. */
        CAPI_SUBTYPE_DIAG_ASG = 59,           /* ASG diagram. */
        CAPI_END = 60                        /* just to mark the end of your enum */
    } CAPI_SUBTYPE_E ;
    
    CAPI_SUBTYPE_E& operator++(CAPI_SUBTYPE_E& capi)
    {
      const int ranges = 2;  // you have 2 invalid ranges in your example
      int invalid[ranges][2] = {{8, 12}, {19, 34}};  // {min, max} (inclusive, exclusive)
    
      CAPI_SUBTYPE_E next = CAPI_SUBTYPE_NULL;
    
      for (int i = 0; i < ranges; i++)
        if ( capi >= invalid[i][0] && capi < invalid[i][1] ) {
          next = static_cast<CAPI_SUBTYPE_E>(invalid[i][1] + 1);
          break;
        } else {
          next = static_cast<CAPI_SUBTYPE_E>(capi + 1);
        }
    
      //  if ( next > CAPI_END )
        // throw an exception
    
      return capi = next;
    }
    
    int main()
    {
      for(CAPI_SUBTYPE_E i = CAPI_SUBTYPE_NULL; i < CAPI_END; ++i)
        cout << i << endl;
    
      cout << endl;
    }
    

    I'm providing only a pre increment operator. A post increment operator is let to be implemanted later.

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

    Somewhat clearer (???) with a bit of boost preprocessing.

    You define your enums by a sequence

    #define CAPI_SUBTYPE_E_Sequence \
        (CAPI_SUBTYPE_NULL)(0)  \
        (CAPI_SUBTYPE_DIAG_DFD)(1) ...
    

    then you can automate (through macros) the declaration of the enum,

    DECL_ENUM(CAPI_SUBTYPE_E) ;
    

    the table that indexes it

    DECL_ENUM_TABLE(CAPI_SUBTYPE_E);
    

    the number of enums / size of the table

    ENUM_SIZE(CAPI_SUBTYPE_E)
    

    and access to it:

    ITER_ENUM_i(i,CAPI_SUBTYPE_E)
    

    Here is the full text.

    #include <boost/preprocessor.hpp>
    
    // define your enum as (name)(value) sequence
    #define CAPI_SUBTYPE_E_Sequence \
        (CAPI_SUBTYPE_NULL)(0)  /* Null subtype. */ \
        (CAPI_SUBTYPE_DIAG_DFD)(1) /* Data Flow diag. */ \
        (CAPI_SUBTYPE_DIAG_ERD)(2)  /* Entity-Relationship diag. */ \
        (CAPI_SUBTYPE_DIAG_DSD)(5) /* Data Structure diag. */ \
        (CAPI_SUBTYPE_DD_ALL)(13) /* DD Entries (All). */
    
    //  # enums
    #define ENUM_SIZE(name) \
        BOOST_PP_DIV(BOOST_PP_SEQ_SIZE(BOOST_PP_CAT(name,_Sequence)),2)
    
    #define ENUM_NAME_N(N,seq) BOOST_PP_SEQ_ELEM(BOOST_PP_MUL(N,2),seq)
    #define ENUM_VALUE_N(N,seq) BOOST_PP_SEQ_ELEM(BOOST_PP_INC(BOOST_PP_MUL(N,2)),seq) 
    
    // declare Nth enum
    #define DECL_ENUM_N(Z,N,seq) \
        BOOST_PP_COMMA_IF(N)   ENUM_NAME_N(N,seq) =  ENUM_VALUE_N(N,seq)
    
    // declare whole enum
    #define DECL_ENUM(name) \
        typedef enum { \
           BOOST_PP_REPEAT( ENUM_SIZE(name) , DECL_ENUM_N , BOOST_PP_CAT(name,_Sequence) ) \
           } name 
    
    DECL_ENUM(CAPI_SUBTYPE_E) ;
    
    // declare Nth enum value
    #define DECL_ENUM_TABLE_N(Z,N,seq) \
        BOOST_PP_COMMA_IF(N)   ENUM_NAME_N(N,seq)
    
    // declare table
    #define DECL_ENUM_TABLE(name) \
        static const name BOOST_PP_CAT(name,_Table) [ENUM_SIZE(name)] = { \
           BOOST_PP_REPEAT( ENUM_SIZE(name) , DECL_ENUM_TABLE_N , BOOST_PP_CAT(name,_Sequence) ) \
           } 
    
    DECL_ENUM_TABLE(CAPI_SUBTYPE_E);
    
    #define ITER_ENUM_i(i,name)  BOOST_PP_CAT(name,_Table) [i] 
    
    // demo 
    // outputs :  [0:0] [1:1] [2:2] [3:5] [4:13]
    #include <iostream>
    
    int main() {
        for (int i=0; i<ENUM_SIZE(CAPI_SUBTYPE_E) ; i++)
            std::cout << "[" << i << ":" << ITER_ENUM_i(i,CAPI_SUBTYPE_E) << "] ";
    
        return 0;
    }
    
    // bonus : check enums are unique and in-order
    
    #include <boost/preprocessor/stringize.hpp>
    #include  <boost/static_assert.hpp>
    
          #define CHECK_ENUM_N(Z,N,seq) \
          BOOST_PP_IF( N , \
          BOOST_STATIC_ASSERT_MSG( \
                ENUM_VALUE_N(BOOST_PP_DEC(N),seq) < ENUM_VALUE_N(N,seq) , \
                   BOOST_PP_STRINGIZE( ENUM_NAME_N(BOOST_PP_DEC(N),seq) ) " not < " BOOST_PP_STRINGIZE( ENUM_NAME_N(N,seq) ) ) \
                   , ) ;
    
    #define CHECK_ENUM(name) \
        namespace { void BOOST_PP_CAT(check_enum_,name) () { \
        BOOST_PP_REPEAT( ENUM_SIZE(name) , CHECK_ENUM_N , BOOST_PP_CAT(name,_Sequence) )  } }
    
    // enum OK
    CHECK_ENUM(CAPI_SUBTYPE_E)
    
    #define Bad_Enum_Sequence \
        (one)(1)\
        (five)(5)\
        (seven)(7)\
        (three)(3)
    
    // enum not OK : enum_iter.cpp(81): error C2338: seven not < three
    CHECK_ENUM(Bad_Enum)
    
    0 讨论(0)
  • 2021-01-31 16:02

    The only real 'solution' I finally came up with to solve this problem is to create a pre-run script that reads the c/c++ file(s) containing the enums and generates a class file that has a list of all the enums as vectors. This is very much the same way Visual Studio supports T4 Templates. In the .Net world it's pretty common practice but since I can't work in that environment I was forced to do it this way.

    The script I wrote is in Ruby, but you could do it in whatever language. If anyone wants the source script, I uploaded it here. It's by no means a perfect script but it fit the bill for my project. I encourage anyone to improve on it and give tips here.

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

    Use Higher Order macros

    Here's the technique we've been using in our projects.

    Concept:

    The idea is to generate a macro called LISTING which contains the definition of name-value pairs and it takes another macro as an argument. In the example below I defined two such helper macros. 'GENERATE_ENUM' to generate the enum and 'GENERATE_ARRAY' to generate an iteratable array. Of course this can be extended as necessary. I think this solution gives you the most bang for the buck. Conceptually it's very similar to iammilind's solution.

    Example:

    // helper macros
    #define GENERATE_ENUM(key,value)       \
          key = value                      \
    
    #define GENERATE_ARRAY(name,value)     \
           name                            \
    
    // Since this is C++, I took the liberty to wrap everthing in a namespace. 
    // This done mostly for aesthetic reasons, you don't have to if you don't want.        
    namespace CAPI_SUBTYPES 
    {
        //  I define a macro containing the key value pairs
        #define LISTING(m)                 \ 
           m(NONE, 0),    /* Note: I can't use NULL here because it conflicts */
           m(DIAG_DFD, 1),                 \
           m(DIAG_ERD, 2),                 \
           ...
           m(DD_ALL, 13),                  \
           m(DD_COUPLE, 14),               \
           ...
                   m(DIAG_SAD, 51),                \
           m(DIAG_ASG, 59),                \
    
        typedef enum {
           LISTING(GENERATE_ENUM)
        } Enum;
    
        const Enum At[] = {
           LISTING(GENERATE_ARRAY)
        };
    
        const unsigned int Count = sizeof(At)/sizeof(At[0]);
    }
    

    Usage:

    Now in code you can refer to the enum like this:

    CAPI_SUBTYPES::Enum eVariable = CAPI_SUBTYPES::DIAG_STD;
    

    You can iterate over the enumeration like this:

    for (unsigned int i=0; i<CAPI_SUBTYPES::Count;  i++) {
         ...
         CAPI_SUBTYPES::Enum eVariable = CAPI_SUBTYPES::At[i];
         ...
    }
    

    Note:

    If memory serves me right, C++11 enums live in their own namespaces (like in Java or C#) , therefore the above usage wouldn't work. You'd have to refer to the enum values like this CAPI_SUBTYPES::Enum::FooBar.

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

    It is about tricky and more C than C++ practice, but you can use X macros. It is very ugly and you need to keep TABLE in right order. In C++ I believe we don't need to iterate over enumerations and more we don't need to assign values to enumeration (ostensibly enumeration value is random in every compilation). So think of it as a joke :)

    #include <iostream>
    
    #define CAPI_SUBTYPE_TABLE \
        CAPI_SUBTYPE_X(CAPI_SUBTYPE_NULL,     0 ) \
        CAPI_SUBTYPE_X(CAPI_SUBTYPE_DIAG_DFD, 1 ) \
        CAPI_SUBTYPE_X(CAPI_SUBTYPE_DD_ALL,   13)
    
    #define CAPI_SUBTYPE_X(name, value) name = value,
    enum CAPI_SUBTYPE
    {
        CAPI_SUBTYPE_TABLE
        CAPI_SUBTYPE_END
    };
    #undef CAPI_SUBTYPE_X
    
    #define CAPI_SUBTYPE_X(name, value) name,
    CAPI_SUBTYPE subtype_iteratable[] =
    {
        CAPI_SUBTYPE_TABLE
        CAPI_SUBTYPE_END
    };
    #undef CAPI_SUBTYPE_X
    
    #define CAPI_SUBTYPE_SIZE  (sizeof(subtype_iteratable) / sizeof(subtype_iteratable[0]) - 1)
    
    
    int main()
    {
        for (unsigned i = 0; i < CAPI_SUBTYPE_SIZE; ++i)
            std::cout << subtype_iteratable[i] << std::endl; // 0, 1, 13
    }
    
    0 讨论(0)
  • 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 <iostream>
    
    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<storage_type>(p) }                      \
                {}                                                           \
                constexpr operator storage_type()                            \
                {  return m;  }                                              \
                template < typename T >                                      \
                constexpr iterable_enum_ operator= (T p)                     \
                {  return { static_cast<storage_type>(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<ParticleEnum>(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 };
    
    0 讨论(0)
提交回复
热议问题