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
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 (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;
}