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

Type Safe Enum in C++

4.00/5 (1 vote)
19 Mar 2013CPOL2 min read 20.8K  
This enum class makes it hard to use it wrongly. Enumeration from one type can’t be mixed with any other data type.

Introduction 

As everybody knows, enums in C++ are, at best, not very useful. A lot of solutions have been proposed around the web, all of them with their advantages.   

The solution proposed in this article, mainly focuses on the type safety aspect of the enumeration. It enforces type checking for each enum, avoiding undesired type conversation.  

This enum class makes it hard to use it wrongly. Enumeration from one type can’t be mixed with any other data type.

How it works

Instead of directly exposing a numeric value, this enum model exposes global class instances. Internally, those classes contain the enum numerical value, but it can’t be accessed from the outside. From here, you have a bunch of class instances, each representing a unique enum value. 

Implementation:  

The design was the easiest part. The implementation was much harder. How do we implement this kind of
model by using a common and shareable code base? The class heritage was not very helpful here. I tried the template, but it didn't work properly. The only viable solution I ended with was the macro. With a set of generic macros, I was able to generate full classes in a standard way.  

The enum macro must be spread between the header file and the source file. 

Serialization 

Mainly because of the serialization process, we must be able to construct an enum object from a corresponding numerical enum value. And we should also be able to get the numeric ID from an existing enum. To meet those requirements, the enum model provides the FromInt( ) and the ToInt( ) functions. 

Extension

This enum model also allows extensible functionality. In the current sample, the ToString( ) function has been added. The bad side of it is, it has to be manually implemented. But it is not mandatory to use the enum. The ToString( ) function can be removed without impact.  

The Problem  

Switch Case… Bad switch case… The switch statement is a real issue for this enum model. Because in C++ only literal values must be supplied inside a switch case statement, I was forced to expose the numerical ID value outside the enum (other than for the serialization). If it wasn't for the switch, I would be able to keep it entirely private. So, to be able to benefit from switch case optimization the numeric enum ID is also public. But, I strongly suggest to not use the ID other than for switch case

Restrictions  

It needs a C++ compiler. It runs under MS-VC and the GCC compiler. In its current form, dynamic libraries are not supported. But this should be fixable.

Using the code    

The real macro definition to include in the header file: 

///////////////////////////////////////////////////////////////////////////
/// Enum macro definition
///////////////////////////////////////////////////////////////////////////
#define DEFINE_ENUM_BEGIN(classname)  \
    class classname \
    { \
    public: \
       typedef int EValue; \
       static classname FromInt(EValue v)  {return classname(v);} \
       classname(const classname& e) : m_value(e.m_value) { }; \
       inline const classname& operator = (const classname& e) { m_value = e.m_value; return *this; }; \
       inline bool operator == (const classname& e) const  { return (m_value == e.m_value); }; \
       inline bool operator != (const classname& e) const  { return (m_value != e.m_value); }; \
       const char* ToString() const; \
       inline int ToInt() const {return (int)m_value;} \
       inline int operator ()() const {return (int)m_value;} 

#define DEFINE_ENUM_TYPE(classname, type, value) const static int type ## _ID = value; const static classname type;
 
#define DEFINE_ENUM_END(classname)  \
   private: \
      explicit classname(const EValue v) : m_value(v) { }; \
      EValue m_value; \
   };
   
 
#define DEFINE_ENUM_IMPL(classname, type) const classname classname::type((classname::EValue)classname::type ## _ID);

Sample enumeration type declaration and implementation:

///////////////////////////////////////////////////////////////////////////
/// Data Type declaration (in the header file .h)
///////////////////////////////////////////////////////////////////////////
DEFINE_ENUM_BEGIN(CDataType)
DEFINE_ENUM_TYPE(CDataType, Unknown, 0)
DEFINE_ENUM_TYPE(CDataType, Bool,    1)
DEFINE_ENUM_TYPE(CDataType, Int,     2)
DEFINE_ENUM_TYPE(CDataType, Float,   3)
DEFINE_ENUM_END(CDataType)
 

///////////////////////////////////////////////////////////////////////////
/// Data Type implementation (in the source file .cpp)
///////////////////////////////////////////////////////////////////////////
DEFINE_ENUM_IMPL(CDataType, Unknown);
DEFINE_ENUM_IMPL(CDataType, Bool);
DEFINE_ENUM_IMPL(CDataType, Int);
DEFINE_ENUM_IMPL(CDataType, Float);
const char* CDataType::ToString() const
{
   switch(m_value)
   {
      case CDataType::Bool_ID :    return "Bool";
      case CDataType::Int_ID :     return "Int";
      case CDataType::Float_ID :   return "Float";
      default: ASSERT(false);      return "Unknown";
   }
}

Usage example: 

void SetDataType(CDataType ty) 
{ 
   m_DataType = ty;
}
SetDataType(CDataType::Int);

CDataType ty(CDataType::Float);
switch(ty()) 
{
    case CDataType::Int_ID   : break;
    case CDataType::Float_ID : break;
    default:
}  

References

Somes interesting links that someone provided (sorry, your name was lost when they moved the article):

License

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