Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Enum Iteration and String Conversion

4.40/5 (10 votes)
7 Oct 2008CPOL6 min read 1   243  
Enum iteration and string conversion

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.

C++
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 strings 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.

C++
// Place in the header file for public access to enum MyEnum 
// and the helper class MyEnumClass
#define MYENUM { enum1, firstEnum=enum1, enum2, enum3, enum4, enum5, 
			totalEnums, errorEnum = 0xFF }
DECLARE_ENUM(MyEnumClass, MyEnum, MYENUM);
    
// Place in source file for implementation of MyEnumClass helper class
IMPLEMENT_ENUM(MyEnumClass);

The code above generates the following (the external declaration for MyEnumClass has been removed for clarity):

C++
enum MyEnum { enum1, firstEnum= enum1, enum2, enum3, 
	enum4, enum5,totalEnums, errorEnum = 0xFF }
MyEnum__EnumClass MyEnumClass;

The enumeration MyEnum can be used as follows:

C++
// conversion functionality
// convert enum to string - outputs "totalEnums"
std::cout << MyEnumClass(totalEnums) << std::endl; 
// convert string to enum - outputs 5
std::cout << MyEnumClass("totalEnums") << std::endl; 

// simple looping
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.

C++
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.

C++
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.

C++
// iteration
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.

C++
#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)); }
  • First parameter is the name of the helper class object - EnumClassName.
    C++
    extern EnumName##__EnumClass EnumClassName;
  • Second Parameter is the name of the enumeration sequence - EnumName
    C++
    enum EnumName ENUM
  • Third parameter is the enumeration set - ENUM
    C++
    enum EnumName ENUM

This will generate the following code (using the example above):

C++
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:

C++
// convert enum to string - outputs "totalEnums"
std::cout << MyEnumClass(totalEnums) << std::endl;	
// convert string to enum - outputs 5
std::cout << MyEnumClass("totalEnums") << std::endl;	

The enum iterator can be access via the base class name and the class object, i.e.

C++
EnumClass<MyEnum>::const_iterator iter = MyEnumClass.begin();
EnumClass<MyEnum>::const_iterator end = MyEnumClass.end();

IMPLEMENT_ENUM

This macro instantiates the helper class.

C++
#define IMPLEMENT_ENUM(EnumClassName) \
	EnumClassName##__EnumClass EnumClassName;
  • First parameter is the name of the helper class object - EnumClassName.
    C++
    EnumName##__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.

C++
// Class construction
EnumClass(const std::string& eString)
	:undefined("undefined")                    { ParseEnumString(eString); }

// Conversion 
const std::string operator()(T eValue) const        { return GetEnumString(eValue); }
const T operator()(const std::string& sStr) const   { return GetEnumValue(sStr); }

// iteration
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()); }
	
// first / last accessors
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.

C++
#define QAZ { 1,2,3,4,5 }
#define ZAQ(P1) char* myString = #P1;

generates the following:

C++
char* myString = "QAZ";

The DEFINE_AS_STR allows the compiler to perform the correct substitution, i.e.

C++
#define QAZ { 1,2,3,4,5 }
#define ZAQ(P1) char* myString = DEFINE_AS_STR(P1);

generates the following:

C++
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:

C++
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 enums 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 enums.

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)