Introduction
Enumerations in C++ are fairly simple. However, in a language that is designed to be strongly-typed, it seems inconsistent to make an exception for enumeration types. For some reason, enumerations are essentially treated as constant integers. This is what I attempted to correct.
I have seen several attempts encapsulating enumerations in classes; however, I have yet to see a simple implementation. Several of them required the use of a compiled utility to create the class. I wanted an implementation that would allow me to use C++ macros to write my enumerations.
Using the code
I separated the macro definitions to make it similar to writing a class. Thus, the definition macros must be used first (e.g., in a header file); however, the implementation macros can be used either right after the definition, or in a source file.
The following is an example taken from the demo project:
#include "Enumeration.h"
BEGIN_ENUM_DEFINITION(Weekdays)
DEFINE_ENUM(SUNDAY)
DEFINE_ENUM(MONDAY)
DEFINE_ENUM(TUESDAY)
DEFINE_ENUM(WEDNESDAY)
DEFINE_ENUM(THURSDAY)
DEFINE_ENUM(FRIDAY)
DEFINE_ENUM(SATURDAY)
END_ENUM_DEFINITION()
#include "MyEnums.h"
BEGIN_ENUM_IMPLEMENTATION(Weekdays)
ENUM_ENTRY(EWeekdays::SUNDAY, "Sunday")
ENUM_ENTRY(EWeekdays::MONDAY, "Monday")
ENUM_ENTRY(EWeekdays::TUESDAY, "Tuesday")
ENUM_ENTRY(EWeekdays::WEDNESDAY, "Wednesday")
ENUM_ENTRY(EWeekdays::THURSDAY, "Thursday")
ENUM_ENTRY(EWeekdays::FRIDAY, "Friday")
ENUM_ENTRY(EWeekdays::SATURDAY, "Saturday")
END_ENUM_IMPLEMENTATION(Weekdays)
The above code creates a class called EWeekdays
that encapsulates an enumeration called Weekdays
with the day names as the constants.
Documentation
BEGIN_ENUM_DEFINITION(theEnumName)
This macro will create a class with the name theEnumName
with a nested enum
declaration named theEnumName
. It also declares a structure to act as a map. This map allows the enumerated value to be converted to a readable string value. Finally, this also declares several member functions.
DEFINE_ENUM_VALUE(enumName, enumValue)
This macro will add an enumerated value named enumName
and set the value to enumValue
.
DEFINE_ENUM(enumName)
This macro will add an enumerated value named enumName
.
DEFINE_LAST_ENUM(enumName)
This macro is the same as the DEFINE_ENUM
macro, but it will leave off the trailing comma. You won't need to use this on most compilers, but technically, it is how ANSI defines the declaration of an enumerated type.
END_ENUM_DEFINITION
This macro ends the declaration of the new enumeration class.
BEGIN_ENUM_IMPLEMENTATION(theEnumName)
This macro starts the implementation of the new enumeration class. theEnumName
must match the name given in the BEGIN_ENUM_DEFINITION
macro.
ENUM_ENTRY(enumValue, enumName)
This macro adds an entry to the map of names and values. It will map the string value in enumName
to the enumValue
integer value. It will also assign a value of 0 to the eData
member of the map.
ENUM_ENTRY_DATA(enumValue, enumName, enumData)
This macro adds an entry to the map in the same manner as the ENUM_ENTRY
macro; however, it allows you to set the data member to some unsigned long
value. That is, it can be some meaningful value to your application, or it can be a pointer to another structure. Keep in mind, the map is static, so if you set this value to a pointer, make sure the object that it points to is valid for as long as your enumeration objects are valid (e.g., in scope).
END_ENUM_IMPLEMENTATION(theEnumName)
This macro finishes the implementation. The value in the theEnumName
must match the value in the BEGIN_ENUM_IMPLEMENTATION
.
Example
The following is an example of what the macros do:
BEGIN_ENUM_DEFINITION(Weekdays)
DEFINE_ENUM(SUNDAY)
DEFINE_ENUM(MONDAY)
DEFINE_ENUM(TUESDAY)
DEFINE_ENUM(WEDNESDAY)
DEFINE_ENUM(THURSDAY)
DEFINE_ENUM(FRIDAY)
DEFINE_ENUM(SATURDAY)
END_ENUM_DEFINITION()
This declaration creates the following definition:
class EWeekdays
{
public:
enum Weekdays;
protected:
Weekdays m_eCurrentValue;
public:
struct SEnumMap
{
Weekdays eVal;
unsigned long eData;
const char* eName;
};
typedef SEnumMap* Position;
typedef SEnumMap* EnumItem;
protected:
static SEnumMap ms_EnumMap[];
public:
EWeekdays()
{ m_eCurrentValue = (Weekdays)0; }
EWeekdays(const EWeekdays& eVal)
{ *this = eVal; }
EWeekdays(const int& iVal)
{ m_eCurrentValue = (Weekdays)iVal; }
const EWeekdays& operator= (const EWeekdays& eVal)
{ m_eCurrentValue = eVal.m_eCurrentValue; return *this; }
const EWeekdays& operator= (const int& eVal)
{ m_eCurrentValue = (Weekdays)eVal; return *this; }
bool operator==(const EWeekdays& eVal) const
{ return m_eCurrentValue == eVal.m_eCurrentValue; }
bool operator!=(const EWeekdays& eVal) const
{ return m_eCurrentValue != eVal.m_eCurrentValue; }
bool operator< (const EWeekdays& eVal) const
{ return m_eCurrentValue < eVal.m_eCurrentValue; }
bool operator> (const EWeekdays& eVal) const
{ return m_eCurrentValue > eVal.m_eCurrentValue; }
bool operator<=(const EWeekdays& eVal) const
{ return (*this < eVal || *this == eVal); }
bool operator>=(const EWeekdays& eVal) const
{ return (*this > eVal || *this == eVal); }
bool operator==(const int& iVal) const
{ return m_eCurrentValue == iVal; }
bool operator!=(const int& iVal) const
{ return m_eCurrentValue != iVal; }
bool operator< (const int& iVal) const
{ return m_eCurrentValue < iVal; }
bool operator> (const int& iVal) const
{ return m_eCurrentValue > iVal; }
bool operator<=(const int& iVal) const
{ return (*this < iVal || *this == iVal); }
bool operator>=(const int& iVal) const
{ return (*this > iVal || *this == iVal); }
operator int() const
{ return (int)m_eCurrentValue; }
operator const char*() const
{ return GetEnumName(); }
const char* GetEnumName() const
{ return GetEnumName(*this); }
static const char* GetEnumName(const int& iVal)
{ return GetEnumName((EWeekdays)iVal); }
unsigned long GetEnumData()
{ return GetEnumData(*this); }
static unsigned long GetEnumData(const int& iVal)
{ return GetEnumData((EWeekdays)iVal); }
static bool IsValidEnum(const int& iVal)
{ return IsValidEnum((EWeekdays)iVal); }
bool IsValidEnum()
{ return IsValidEnum(*this); }
static const char* GetEnumName(const EWeekdays& eVal);
static unsigned int GetCount();
static unsigned long GetEnumData(const EWeekdays& eVal);
static EnumItem FindName(const char* name);
static bool IsValidEnum(const EWeekdays& eVal);
static Position GetFirstEnumPosition();
static const EnumItem GetNextEnumPosition(Position& pos);
enum Weekdays
{
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
};
};
You should notice that the static map ms_EnumMap
is un-sized, and the static const char* GetEnumName(const EWeekdays& eVal)
, static unsigned int GetCount()
, static unsigned long GetEnumData(const EWeekdays& eVal)
, static EnumItem FindName(const char* name)
, static bool IsValidEnum(const EWeekdays& eVal)
, static Position GetFirstEnumPosition()
, and static const EnumItem GetNextEnumPosition(Position& pos)
functions are not implemented. These steps are accomplished in the implementation macros below:
BEGIN_ENUM_IMPLEMENTATION(Weekdays)
ENUM_ENTRY(EWeekdays::SUNDAY, "Sunday")
ENUM_ENTRY(EWeekdays::MONDAY, "Monday")
ENUM_ENTRY(EWeekdays::TUESDAY, "Tuesday")
ENUM_ENTRY(EWeekdays::WEDNESDAY, "Wednesday")
ENUM_ENTRY(EWeekdays::THURSDAY, "Thursday")
ENUM_ENTRY(EWeekdays::FRIDAY, "Friday")
ENUM_ENTRY(EWeekdays::SATURDAY, "Saturday")
END_ENUM_IMPLEMENTATION(Weekdays)
The above set of macros implements the last steps of the enumeration class:
EWeekdays::SEnumMap EWeekdays::ms_EnumMap[] =
{
{ EWeekdays::SUNDAY, 0, "Sunday" },
{ EWeekdays::MONDAY, 0, "Monday" },
{ EWeekdays::TUESDAY, 0, "Tuesday" },
{ EWeekdays::WEDNESDAY, 0, "Wednesday" },
{ EWeekdays::THURSDAY, 0, "Thursday" },
{ EWeekdays::FRIDAY, 0, "Friday" },
{ EWeekdays::SATURDAY, 0, "Saturday" },
};
const char* EWeekdays::GetEnumName(const EWeekdays& eVal)
{
Position pos = GetFirstEnumPosition();
while (0 != pos)
{
EnumItem pVal = GetNextEnumPosition(pos);
if (pVal->eVal == eVal)
return pVal->eName;
}
return 0;
}
unsigned int EWeekdays::GetCount()
{ return (unsigned int)(sizeof(ms_EnumMap) / sizeof(ms_EnumMap[0])); }
unsigned long EWeekdays::GetEnumData(const EWeekdays& eVal)
{
Position pos = GetFirstEnumPosition();
while (0 != pos)
{
EnumItem pVal = GetNextEnumPosition(pos);
if (pVal->eVal == eVal)
return pVal->eData;
}
return 0;
}
EWeekdays::EnumItem EWeekdays::FindName(const char* name)
{
Position pos = GetFirstEnumPosition();
while (0 != pos)
{
EnumItem pVal = GetNextEnumPosition(pos);
if (0 == strcmp(name, pVal->eName))
return pVal;
}
return 0;
}
bool EWeekdays::IsValidEnum(const EWeekdays& eVal)
{
Position pos = GetFirstEnumPosition();
while (0 != pos)
{
EnumItem pVal = GetNextEnumPosition(pos);
if (pVal->eVal == eVal)
return true;
}
return false;
}
EWeekdays::Position EWeekdays::GetFirstEnumPosition()
{
return &ms_EnumMap[0];
}
const EWeekdays::EnumItem EWeekdays::GetNextEnumPosition(EWeekdays::Position& pos)
{
EnumItem ret = pos;
if (pos < &ms_EnumMap[GetCount() - 1])
pos++;
else
pos = 0;
return ret;
}
Points of Interest
You should note that you can make multiple enum
entries that have the same integer values; however, when using the (const char*)
operator to get the name of them, it will return the string corresponding to the first one found. If you do create multiple entries, you should use the same string for them as well.
This revision no longer suffers from the limitation of having to start at 0. Now, not only can you start anywhere, but the enumeration values do not have to be concurrent.
BEGIN_ENUM_DEFINITION(FoodGroups)
DEFINE_ENUM_VALUE(GRAIN, -2)
DEFINE_ENUM_VALUE(MEAT, 4)
DEFINE_ENUM_VALUE(FRUIT, 9)
DEFINE_ENUM(VEGETABLE)
END_ENUM_DEFINITION()
The code above declares an enumeration with values at -2, 4, 9, and 10. To accommodate this, I added GetFirstEnumerationPosition
and GetNextEnumerationPosition
functions. If you are defining enumerations this way, you will need to use these functions to iterate through the enumeration. If your numbers are concurrent, you can still use a for
-style loop if you like, though.
Since several of you requested a way to go from strings to enumerations, I came up with a way to accommodate that ability without making it too much of a headache. The FindName
member function will iterate through the enumeration and attempt to find a match for the name (keep in mind, it will stop on the first match it finds). If successful, it returns a EnumItem
(which is a typedef
for a SEnumMap*
). If it fails, it returns 0. I realize this falls a half-step short of an assignment operator for strings, but I needed some way to do error checking. To do an assignment using this, you would do something like the following:
EWeekday Day = EWeekday::SUNDAY;
const char* MyDay = "Saturday";
EWeekdays::EnumItem item = EWeekdays::FindName(MyDay);
if (0 != item)
{
Day = item->eVal;
}
else
{
}
You will also notice the addition of three IsEnumValid
member functions. These functions allow you to check to see if a particular value is valid for this enumeration.
History
- 2005/01/30 - Original.
- 2005/02/04 - Uploaded correct sample code and added ANSI-compliant definition macro.
- 2005/02/09 - Added some significant features (data member for map, name search, iteration functions, validity checks).