Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Object Oriented Enumerations

0.00/5 (No votes)
10 Feb 2005 1  
An article on using macros to encapsulate enumerations.

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:

// from MyEnums.h

#include "Enumeration.h"


// the EWeekdays definition

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()
// from MyEnums.cpp

#include "MyEnums.h"


// the EWeekdays implementation

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.

// from MyEnums.h

// the EFoodGroups defintion

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
{
    // not found, do something here

}

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).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here