Iterating over non-incremental Enum

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

    Since an enum does not allow iteration you have to create an alternative representation of the enum and its range of values.

    The approach that I would take would be a simple table lookup embedded in a class. The problem is that as the API modifies its enum with new entries, you would also need to update the constructor for this class.

    The simple class that I would use would be comprised of a constructor to build the table along with a couple of methods to iterate over the table. Since you also want to know if there is a problem with table size when adding items, you could use an assert () macro that would issue an assert() in debug mode. In the source example below I use the preprocessor to test for debug compile or not and whether assert has been included or not so as to provide a mechanism for basic consistency checking.

    I have borrowed an idea I saw from P. J. Plauger in his book Standard C Library of using a simple lookup table for ANSI character operations in which the character is used to index into a table.

    To use this class you would do something like the following which uses a for loop to iterate over the set of values in the table. Within the body of the loop you would do whatever you want to do with the enum values.

    CapiEnum myEnum;
    
    for (CAPI_SUBTYPE_E jj = myEnum.Begin(); !myEnum.End(); jj = myEnum.Next()) {
         // do stuff with the jj enum value
    }
    

    Since this class enumerates over the values, I have arbitrarily chosen to return the value of CAPI_SUBTYPE_NULL in those cases where we have reached the end of the enumeration. So the return value in the case of a table lookup error is in the valid range however it can not be depended upon. There fore a check on the End() method should be done to see if the end of an iteration has been reached. Also after doing the construction of the object one can check the m_bTableError data member to see if there was an error during construction.

    The source example for the class follows. You would need to update the constructor with the enum values for the API as they change. Unfortunately there is not much that can be done to automate the check on an update enum however we do have tests in place in a debug compile to check that the table is large enough and that the value of the enum being put into the table is within range of the table size.

    class CapiEnum {
    public:
        CapiEnum (void);                                // constructor
        CAPI_SUBTYPE_E  Begin (void);                   // method to call to begin an iteration
        CAPI_SUBTYPE_E  Next (void);                    // method to get the next in the series of an iteration
        bool            End (void);                     // method to indicate if we have reached the end or not
        bool            Check (CAPI_SUBTYPE_E value);   // method to see if value specified is in the table
        bool  m_TableError;
    private:
        static const int m_TableSize = 256;    // set the lookup table size
        static const int m_UnusedTableEntry = -1;
        int   m_iIterate;
        bool  m_bEndReached;
        CAPI_SUBTYPE_E  m_CapiTable[m_TableSize];
    };
    
    #if defined(_DEBUG)
    #if defined(assert)
    #define ADD_CAPI_ENUM_ENTRY(capi) (((capi) < m_TableSize && (capi) > m_UnusedTableEntry) ? (m_CapiTable[(capi)] = (capi)) : assert(((capi) < m_TableSize) && ((capi) > m_UnusedTableEntry)))
    #else
    #define ADD_CAPI_ENUM_ENTRY(capi) (((capi) < m_TableSize && (capi) > m_UnusedTableEntry) ? (m_CapiTable[(capi)] = (capi)) : (m_TableError = true))
    #endif
    #else
    #define ADD_CAPI_ENUM_ENTRY(capi) (m_CapiTable[(capi)] = (capi))
    #endif
    
    CapiEnum::CapiEnum (void) : m_bEndReached(true), m_iIterate(0), m_TableError(false)
    {
        for (int iLoop = 0; iLoop < m_TableSize; iLoop++) m_CapiTable[iLoop] = static_cast <CAPI_SUBTYPE_E> (m_UnusedTableEntry);
        ADD_CAPI_ENUM_ENTRY(CAPI_SUBTYPE_NULL);
        // .....
        ADD_CAPI_ENUM_ENTRY(CAPI_SUBTYPE_DIAG_ASG);
    }
    
    CAPI_SUBTYPE_E CapiEnum::Begin (void)
    {
        m_bEndReached = false;
        for (m_iIterate = 0; m_iIterate < m_TableSize; m_iIterate++) {
            if (m_CapiTable[m_iIterate] > m_UnusedTableEntry) return m_CapiTable[m_iIterate];
        }
        m_bEndReached = true;
        return CAPI_SUBTYPE_NULL;
    }
    
    CAPI_SUBTYPE_E CapiEnum::Next (void)
    {
        if (!m_bEndReached) {
            for (m_iIterate++; m_iIterate < m_TableSize; m_iIterate++) {
                if (m_CapiTable[m_iIterate] > m_UnusedTableEntry) return m_CapiTable[m_iIterate];
            }
        }
        m_bEndReached = true;
        return CAPI_SUBTYPE_NULL;
    }
    
    bool CapiEnum::End (void)
    {
        return m_bEndReached;
    }
    
    bool CapiEnum::Check (CAPI_SUBTYPE_E value)
    {
        return (value >= 0 && value < m_TableSize && m_CapiTable[value] > m_UnusedTableEntry);
    }
    

    And if you like you could add an additional method to retrieve the current value of the iteration. Notice that rather than incrementing to the next, the Current() method uses whatever the iteration index is currently at and starts searching from the current position. So if the current position is a valid value it just returns it otherwise it will find the first valid value. Alternatively, you could make this just return the current table value pointed to by the index and if the value is invalid, set an error indicator.

    CAPI_SUBTYPE_E CapiEnum::Current (void)
    {
        if (!m_bEndReached) {
            for (m_iIterate; m_iIterate < m_TableSize; m_iIterate++) {
                if (m_CapiTable[m_iIterate] > m_UnusedTableEntry) return m_CapiTable[m_iIterate];
            }
        }
        m_bEndReached = true;
        return CAPI_SUBTYPE_NULL;
    }
    
    0 讨论(0)
  • 2021-01-31 16:13

    Here's one more approach. One bonus is that your compiler may warn you if you omit an enum value in a switch:

    template<typename T>
    void IMP_Apply(const int& pSubtype, T& pApply) {
        switch (pSubtype) {
            case CAPI_SUBTYPE_NULL :
            case CAPI_SUBTYPE_DIAG_DFD :
            case CAPI_SUBTYPE_DIAG_ERD :
            case CAPI_SUBTYPE_DIAG_STD :
            case CAPI_SUBTYPE_DIAG_STC :
            case CAPI_SUBTYPE_DIAG_DSD :
            case CAPI_SUBTYPE_SPEC_PROCESS :
            case CAPI_SUBTYPE_SPEC_MODULE :
            case CAPI_SUBTYPE_SPEC_TERMINATOR :
            case CAPI_SUBTYPE_DD_ALL :
            case CAPI_SUBTYPE_DD_COUPLE :
            case CAPI_SUBTYPE_DD_DATA_AREA :
            case CAPI_SUBTYPE_DD_DATA_OBJECT :
            case CAPI_SUBTYPE_DD_FLOW :
            case CAPI_SUBTYPE_DD_RELATIONSHIP :
            case CAPI_SUBTYPE_DD_STORE :
            case CAPI_SUBTYPE_DIAG_PAD :
            case CAPI_SUBTYPE_DIAG_BD :
            case CAPI_SUBTYPE_DIAG_UCD :
            case CAPI_SUBTYPE_DIAG_PD :
            case CAPI_SUBTYPE_DIAG_COD :
            case CAPI_SUBTYPE_DIAG_SQD :
            case CAPI_SUBTYPE_DIAG_CD :
            case CAPI_SUBTYPE_DIAG_SCD :
            case CAPI_SUBTYPE_DIAG_ACD :
            case CAPI_SUBTYPE_DIAG_CPD :
            case CAPI_SUBTYPE_DIAG_DPD :
            case CAPI_SUBTYPE_DIAG_PFD :
            case CAPI_SUBTYPE_DIAG_HIER :
            case CAPI_SUBTYPE_DIAG_IDEF0 :
            case CAPI_SUBTYPE_DIAG_AID :
            case CAPI_SUBTYPE_DIAG_SAD :
            case CAPI_SUBTYPE_DIAG_ASG :
                /* do something. just `applying`: */
                pApply(static_cast<CAPI_SUBTYPE_E>(pSubtype));
                return;
        }
    
        std::cout << "Skipped: " << pSubtype << '\n';
    }
    
    template<typename T>
    void Apply(T& pApply) {
        const CAPI_SUBTYPE_E First(CAPI_SUBTYPE_NULL);
        const CAPI_SUBTYPE_E Last(CAPI_SUBTYPE_DIAG_ASG);
    
        for (int idx(static_cast<int>(First)); idx <= static_cast<int>(Last); ++idx) {
            IMP_Apply(idx, pApply);
        }
    }
    
    int main(int argc, const char* argv[]) {
        class t_apply {
        public:
            void operator()(const CAPI_SUBTYPE_E& pSubtype) const {
                std::cout << "Apply: " << static_cast<int>(pSubtype) << '\n';
            }
        };
        t_apply apply;
        Apply(apply);
        return 0;
    }
    
    0 讨论(0)
  • 2021-01-31 16:15

    I'm using this type of constructions to define my own enums:

    #include <boost/unordered_map.hpp>
    
    namespace enumeration
    {
    
       struct enumerator_base : boost::noncopyable
       {
          typedef
             boost::unordered_map<int, std::string>
             kv_storage_t;
          typedef
             kv_storage_t::value_type
             kv_type;
          typedef
             std::set<int>
             entries_t;
          typedef
             entries_t::const_iterator
             iterator;
          typedef
             entries_t::const_iterator
             const_iterator;
          kv_storage_t const & kv() const
          {
             return storage_;
          }
    
          const char * name(int i) const
          {
             kv_storage_t::const_iterator it = storage_.find(i);
             if(it != storage_.end())
                return it->second.c_str();
             return "empty";
          }
    
          iterator begin() const
          {
             return entries_.begin();
          }
    
          iterator end() const
          {
             return entries_.end();
          }
    
          iterator begin()
          {
             return entries_.begin();
          }
    
          iterator end()
          {
             return entries_.end();
          }
    
          void register_e(int val, std::string const & desc)
          {
             storage_.insert(std::make_pair(val, desc));
             entries_.insert(val);
          }
       protected:
          kv_storage_t storage_;
          entries_t entries_;
       };
    
       template<class T>
       struct enumerator;
    
       template<class D>
       struct enum_singleton : enumerator_base
       {
          static enumerator_base const & instance()
          {
             static D inst;
             return inst;
          }
       };
    }
    
    #define QENUM_ENTRY(K, V, N)  K, N register_e((int)K, V);
    #define QENUM_ENTRY_I(K, I, V, N)  K = I, N register_e((int)K, V);
    
    #define QBEGIN_ENUM(NAME, C)   \
    enum NAME                     \
    {                             \
       C                          \
    }                             \
    };                            \
    }                             \
    
    #define QEND_ENUM(NAME) \
    };                     \
    namespace enumeration  \
    {                      \
    template<>             \
    struct enumerator<NAME>\
       : enum_singleton< enumerator<NAME> >\
    {                      \
       enumerator()        \
       {
    
    
    QBEGIN_ENUM(test_t,
       QENUM_ENTRY(test_entry_1, "number uno",
       QENUM_ENTRY_I(test_entry_2, 10, "number dos",
       QENUM_ENTRY(test_entry_3, "number tres",
    QEND_ENUM(test_t)))))
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
       BOOST_FOREACH(int x, enumeration::enumerator<test_t>::instance())
          std::cout << enumeration::enumerator<test_t>::instance().name(x) << "=" << x << std::endl;
       return 0;
    }
    

    Also you can replace storage_ type to boost::bimap to have bidirectional correspondance int <==> string

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

    There are a lot of answers to this question already, but most of them are either very complicated or inefficient in that they don't directly address the requirement of iterating over an enum with gaps. Everyone so far has said that this is not possible, and they are sort of correct in that there is no language feature to allow you to do this. That certainly does not mean you can't, and as we can see by all the answers so far, there are many different ways to do it. Here is my way, based on the enum you have provided and the assumption that it's structure won't change much. Of course this method can be adapted as needed.

    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_SUBTYPE_E;
    
    struct ranges_t
    {
        int start;
        int end;
    };
    ranges_t ranges[] =
    {
        {CAPI_SUBTYPE_NULL,         CAPI_SUBTYPE_NULL},
        {CAPI_SUBTYPE_DIAG_DFD,     CAPI_SUBTYPE_DIAG_DSD},
        {CAPI_SUBTYPE_SPEC_PROCESS, CAPI_SUBTYPE_SPEC_TERMINATOR},
        {CAPI_SUBTYPE_DD_ALL,       CAPI_SUBTYPE_DD_STORE},
        {CAPI_SUBTYPE_DIAG_PAD,     CAPI_SUBTYPE_DIAG_SAD},
        {CAPI_SUBTYPE_DIAG_ASG,     CAPI_SUBTYPE_DIAG_ASG},
    };
    int numRanges = sizeof(ranges) / sizeof(*ranges);
    
    for( int rangeIdx = 0; rangeIdx < numRanges; ++rangeIdx )
    {
        for( int enumValue = ranges[rangeIdx].start; enumValue <= ranges[rangeIdx].end; ++enumValue )
        {
            processEnumValue( enumValue );
        }
    }
    

    Or something along those lines.

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

    With C++, the only way to iterate through enums is store them in an array and iterate through the same. The main challenge is how to track the same order in the enum declaration and the array declaration?
    You can automate the way you order them in the enum as well as array. I feel that this is a decent way:

    // CAPI_SUBTYPE_E_list.h
    // This header file contains all the enum in the order
    // Whatever order is set will be followed everywhere
    NAME_VALUE(CAPI_SUBTYPE_NULL, 0),         /* Null subtype. */
    NAME_VALUE(CAPI_SUBTYPE_DIAG_DFD, 1),     /* Data Flow diag. */
    NAME_VALUE(CAPI_SUBTYPE_DIAG_ERD, 2),     /* Entity-Relationship diag. */
    ...
    NAME_VALUE(CAPI_SUBTYPE_DD_ALL, 13),      /* DD Entries (All). */
    NAME_VALUE(CAPI_SUBTYPE_DD_COUPLE, 14),   /* DD Entries (Couples). */
    ...
    NAME_VALUE(CAPI_SUBTYPE_DIAG_ASG, 59)     /* ASG diagram. */
    

    Now you #include this file in your enum declaration and array declaration both places with macro redefinition:

    // Enum.h
    typedef enum {
    #define NAME_VALUE(NAME,VALUE) NAME = VALUE
    #include"CAPI_SUBTYPE_E_list.h"
    #undef NAME_VALUE
    }CAPI_SUBTYPE_E;
    

    And put the same file for array with other macro definition:

    // array file
    // Either this array can be declared `static` or inside unnamed `namespace` to make 
    // ... it visible through a header file; Or it should be declared `extern` and keep ...
    // ...  the record of its size; declare a getter method for both array and the size
    unsigned int CAPI_SUBTYPE_E_Array [] = {
    #define NAME_VALUE(NAME,VALUE) NAME
    #include"CAPI_SUBTYPE_E_list.h"
    #undef NAME_VALUE
    };
    

    Now iterate in C++03 as:

    for(unsigned int i = 0, size = sizeof(CAPI_SUBTYPE_E_Array)/sizeof(CAPI_SUBTYPE_E_Array[0]);
        i < size; ++i)
    

    or yet simple in C++11:

    for(auto i : CAPI_SUBTYPE_E_Array)
    
    0 讨论(0)
  • 2021-01-31 16:21

    You cannot iterate over arbitrary enum in C++. For iterating, values should be put in some container. You can automate maintaining such a container using 'enum classes' as described here: http://www.drdobbs.com/when-enum-just-isnt-enough-enumeration-c/184403955http://www.drdobbs.com/when-enum-just-isnt-enough-enumeration-c/184403955

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