Introduction
Ever wanted to iterate through a series of enumerations? This was the situation I found myself in. Presented with an enumeration sequence, perform a specific task. After a few minutes, I generated the following code snippet. It is simple and effective.
enum MyEnum { enum1, firstEnum=enum1, enum2, enum3, enum4, enum5,
totalEnums, errorEnum = 0xFF };
const MyEnum& operator++(MyEnum& eValue) { return (eValue = (MyEnum)(eValue+1)); }
for(MyEnum eValue = firstEnum ; eValue < totalEnums; ++eValue)
DoSomething(eValue);
However, this implementation suffers from a number of fundamental problems:
- It will only work for simple sequential enumerations.
- It requires additional knowledge about the enumeration sequence, i.e. the start and end points.
In addition to iteration, string
conversion functionality is useful, i.e. enabling the conversion of string
s to an enumeration value, and vice-versa.
The following short article aims to provide a simple mechanism for providing this functionality.
The EnumClass
The EnumClass<>
provides the functionality identified in the introduction. It provides iteration and string
conversion functionality for C++ style enumerations. The class does require two helper macros to declare and implement the enumeration and the helper class itself, but with the following three simple lines of code, the enum
and helper class are defined and implemented.
#define MYENUM { enum1, firstEnum=enum1, enum2, enum3, enum4, enum5,
totalEnums, errorEnum = 0xFF }
DECLARE_ENUM(MyEnumClass, MyEnum, MYENUM);
IMPLEMENT_ENUM(MyEnumClass);
The code above generates the following (the external declaration for MyEnumClass
has been removed for clarity):
enum MyEnum { enum1, firstEnum= enum1, enum2, enum3,
enum4, enum5,totalEnums, errorEnum = 0xFF }
MyEnum__EnumClass MyEnumClass;
The enumeration MyEnum
can be used as follows:
std::cout << MyEnumClass(totalEnums) << std::endl;
std::cout << MyEnumClass("totalEnums") << std::endl;
for(MyEnums eValue = firstEnum; eValue <= totalEnums; ++eValue)
DoSomething(eValue);
More Complex Enumerations
The for
loop implementation will correctly sequence through enumerations that are sequential, it will also handle non consecutive enumerations, i.e.
enum MyEnum { firstEnum = 0, enum1 = firstEnum, enum2=1, enum3=2, enum3=4,
enum4=8, enum5 = 16, totalEnums = enum5, errorEnum = 0xFF }
However, the simple for
loop is not able to guarantee handling non sequential enumerations (See Caveats later), e.g.
enum MyEnum { firstEnum = 0, enum1 = firstEnum, enum2=16, enum3=1, enum3=2,
enum4=4, enum5 = 8, totalEnums = enum5, errorEnum = 0xFF }
To overcome this problem, EnumClass<>
provides iterators that can correctly sequence these more complex enumeration sequences. The following code snippet shows how to use the EnumClass<>::const_iterator
to sequence through more complex enumeration sequences.
EnumClass<MyEnum>::const_iterator iter = MyEnumsEnumClass.begin();
EnumClass<MyEnum>::const_iterator end = MyEnumsEnumClass.end();
while(iter != end)
{
DoSomething(*iter);
iter++;
};
Implementation
The following is a short description of the EnumClass
and the helper macros DECLARE_ENUM
& IMPLEMENT_ENUM
.
DECLARE_ENUM
This macro declares the enumeration, the increment operators and the extern
reference for helper class.
#define DECLARE_ENUM(EnumClassName, EnumName, ENUM) \
enum EnumName ENUM; \
class EnumClassName##__EnumClass: public EnumClass<MyEnum> \
{ \
public: \
EnumClassName##__EnumClass(): EnumClass<MyEnum>(DEFINE_AS_STR(ENUM)) {}; \
}; \
extern EnumClassName##__EnumClass EnumClassName; \
inline const EnumName& operator++(EnumName& eValue)
{ return (eValue = EnumClassName.GetNext(eValue)); } \
inline const EnumName& operator++(EnumName& eValue, int)
{ return (eValue = EnumClassName.GetNext(eValue)); }
This will generate the following code (using the example above):
extern MyEnum__EnumClass MyEnumClass;
enum MyEnum { enum1, firstEnum= enum1, enum2, enum3, enum4, enum5,
totalEnums, errorEnum = 0xFF }
The class MyEnum__EnumClass
is also defined as part of the macro. This class is a wrapper for the EnumClass<>
, and is simply used as a mechanism for supplying the stringized enumeration to the EnumClass<>
. It does not need to be used to access any of the enum
functionality. See DEFINE_AS_STR
section for further information regarding stringizing the enumeration.
The enum
conversion functionality can be accessed via the class object name, i.e. for the example above:
std::cout << MyEnumClass(totalEnums) << std::endl;
std::cout << MyEnumClass("totalEnums") << std::endl;
The enum
iterator can be access via the base class name and the class object, i.e.
EnumClass<MyEnum>::const_iterator iter = MyEnumClass.begin();
EnumClass<MyEnum>::const_iterator end = MyEnumClass.end();
IMPLEMENT_ENUM
This macro instantiates the helper class.
#define IMPLEMENT_ENUM(EnumClassName) \
EnumClassName##__EnumClass EnumClassName;
EnumClass
The class provides the conversion and iteration functionality for any enumeration sequence.
It is a template class providing name space resolution, based on the unique enumeration identity, i.e. a different and unique EnumClass<>
is generated for each enumeration, thereby preventing name space clashes.
The interface to the EnumClass<>
is simple.
EnumClass(const std::string& eString)
:undefined("undefined") { ParseEnumString(eString); }
const std::string operator()(T eValue) const { return GetEnumString(eValue); }
const T operator()(const std::string& sStr) const { return GetEnumValue(sStr); }
const_iterator begin() const { return const_iterator(enumList.begin()); }
const_iterator end() const { return const_iterator(enumList.end()); }
const_reverse_iterator rbegin() const
{ return const_reverse_iterator(enumList.rbegin()); }
const_reverse_iterator rend() const
{ return const_reverse_iterator(enumList.rend()); }
const T first() const { return enumList.front(); }
const T last() const { return enumList.last(); }
size_t size() const { return enumList.size(); }
The EnumClass<>
holds a vector of EnumInfo
objects. These contain the mapping between enumeration values and string
equivalents. The list of EnumInfo
objects is generated during the class construction. The ParseEnumString()
method splits the enumeration string
into individual name, value pairs. The name is stored as a name for the enum
, whilst the value is evaluated to generate an integer value for the enumeration. These name value pairs are then added to the list of EnumInfo
objects. If an EnumInfo
object already exists with the same value, the name is added to the existing EnumInfo
object, and the new EnumInfo
object discarded.
Additional Information
Enum Evaluation
The value assigned to an
EnumInfo
object is sequential, starting at zero, if no value is defined. If a value is defined, then it is evaluated and assigned to the
EnumInfo
object. The evaluation logic allows for simple algebraic expressions (including the use of braces). The following operators can be handled:
- + add
- - subtract
- * multiply
- / divide
- ^ exclusive OR
- & arithmetic AND
- | arithmetic OR
- % modulus
The evaluation should handle 99% of all expressions, however evaluation is strictly left to right, i.e. no precedence is performed, see caveats for more details.
Enum Values
#define ENUM
To ensure the enum
sequence (multiple commas, hence multiple parameters) works using standard macros, a fixed number of macro parameters is required. This is achieved by defining the enum
sequence as a macro. This enum
macro can then be treated as a single macro parameter wherever it is used. The body of the macro can contain as many commas as required to define the enumeration sequence and still be treated as a single macro parameter.
DEFINE_AS_STR
A problem was encountered when trying to stringize the ENUM #define
. Compilers do not appear to perform the #define
substitution expected, the #define
name is stringized, not its content.
#define QAZ { 1,2,3,4,5 }
#define ZAQ(P1) char* myString = #P1;
generates the following:
char* myString = "QAZ";
The DEFINE_AS_STR
allows the compiler to perform the correct substitution, i.e.
#define QAZ { 1,2,3,4,5 }
#define ZAQ(P1) char* myString = DEFINE_AS_STR(P1);
generates the following:
char* myString = "{ 1,2,3,4,5 }";
I cannot lay claim to discovering this gem, the idea was lifted from the boost
library. So I am not sure how it works, but it does ensure the correct substitutions are made.
Caveats
for loop
The pre and post increment operators work correctly, however it should be noted that unexpected results may occur if they are used with non-sequential enumeration. Consider the following:
enum MyEnums { firstEnum = 0, enum1 = firstEnum, enum2=1, enum3=16, enum3=2,
enum4=4, enum5 = 8, totalEnums = enum5, errorEnum = 0xFF }
for(MyEnums eValue = firstEnum; eValue < totalEnums; ++eValue)
DoSomething(eValue)
The increment operators will sequence through the enum
s as follows: enum1
, enum2
, enum3
, however, when eValue
becomes enum3
(eValue = 2
) this is less than totalEnums
, i.e. 16 > 8, and the for
loop will exit. There are two solutions, firstly do not use the <
operator, use ! =
operator, or use the const_iterator
provided by EnumClass
. Note: use of the !=
operator does require that the end value is unique. Care should be taken to ensure the for
loop will work as expected when iterating through non sequential enum
s.
Expression Evaluation
The EnumClass
uses a very simple evaluation mechanism, which evaluates in a left to right sequence. It does not perform any precedence on the value expressions, therefore:
A+B*C will not evaluate correctly, use braces to ensure correct evaluation, i.e. (A+B)*C
Conclusion
I've tried to use standard C++ within this implementation, but having only compiled this using a Microsoft compiler, it would be interesting to hear if it compiles successfully with other compilers/platforms. I suspect there will be an issue over the stringizer macro, but minds greater than mine should be able to resolve that issue.
The two caveats identified above are work for the future releases; I am not sure there is a simple answer to the for
loop issue, however if there is sufficient demand the expression evaluation should be resolvable.
Hopefully the class will be of some use. Enjoy!
History
- 06 Oct 2008: Original article