More C++ Idioms/Type Safe Enum

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Type Safe Enum
[edit | edit source]

Intent[edit | edit source]

Improve type-safety of native enum data type in C++.

Also Known As[edit | edit source]

Typed enums

Motivation[edit | edit source]

Enumerations in C++03 are not sufficiently type-safe and may lead to unintended errors. In spite of being a language-supported feature, enums also have code portability issues due to different ways in which different compilers handle the corner cases of enumerations. The problems surrounding enumerations can be categorized in 3 ways[1]:

  • Implicit conversion to an integer
  • Inability to specify the underlying type (prior to c++11) [2]
  • Scoping issues

C++03 enumerations are not devoid of type safety, however. In particular, direct assignment of one enumeration type to another is not permitted. Moreover, there is no implicit conversion from an integer value to an enumeration type. However, most unintended enumeration errors can be attributed to its ability to get automatically promoted to integers. For instance, consider the following valid C++03 program. Only a few compilers such as GNU g++ issue a warning to prevent unintended errors like below.

enum color { red, green, blue };
enum shape { circle, square, triangle };

color c = red;
bool flag = (c >= triangle); // Unintended!

Other problems with C++03 enums are their inability to specify the underlying representation type to hold the values of enumerators. A side effect of this underspecification is that the sizes and the signedness of the underlying type vary from compiler to compiler, which leads to non-portable code. Finally, the scoping rules of C++03 may cause inconvenience. For instance, two enumerations in the same scope can not have enumerators with the same name.

Type safe enum idiom is a way to address these issues concerning C++03 enumerations. Note that enum class in C++11 eliminates the need of this idiom.

Solution and Sample Code[edit | edit source]

Type safe enum idiom wraps the actual enumerations in a class or a struct to provide strong type-safety.

template<typename def, typename inner = typename def::type>
class safe_enum : public def
{
  typedef inner type;
  inner val;

public:

  safe_enum() {}
  safe_enum(type v) : val(v) {}
  type underlying() const { return val; }

  friend bool operator == (const safe_enum & lhs, const safe_enum & rhs) { return lhs.val == rhs.val; }
  friend bool operator != (const safe_enum & lhs, const safe_enum & rhs) { return lhs.val != rhs.val; }
  friend bool operator <  (const safe_enum & lhs, const safe_enum & rhs) { return lhs.val <  rhs.val; }
  friend bool operator <= (const safe_enum & lhs, const safe_enum & rhs) { return lhs.val <= rhs.val; }
  friend bool operator >  (const safe_enum & lhs, const safe_enum & rhs) { return lhs.val >  rhs.val; }
  friend bool operator >= (const safe_enum & lhs, const safe_enum & rhs) { return lhs.val >= rhs.val; }
};

struct color_def {
  enum type { red, green, blue };
};
typedef safe_enum<color_def> color;

struct shape_def {
  enum type { circle, square, triangle };
};
typedef safe_enum<shape_def, unsigned char> shape; // unsigned char representation

int main(void)
{
  color c = color::red;
  bool flag = (c >= shape::triangle); // Compiler error.
}

In the above solution, the actual enumerations are wrapped inside color_def and shape_def structures. To obtain a safe enumeration type from the definitions, safe_enum template is used. safe_enum template makes use of the Parameterized Base Class idiom i.e., it inherits publicly from the def parameter inself. As a result, the enumerations defined in the definitions are available in the safe_enum instantiation.

safe_enum template also supports a way to specify the underlying type (inner) used to hold values of the enumerators. By default it is the same as the enumeration type in its definition. Using explicit underlying representation type as the second type parameter, the size of the safe_enum template instantiation can be controlled in a portable way.

safe_enum template prevents automatic promotion to integers because there is no conversion operator in it. Instead, it provides underlying() function, which can be used to retrieve the value of the enumerator. Overloaded operators are also provided by the template to allow simple comparisons and total ordering of the type-safe enumerators.

Iterating over enumerations

In some cases where enumerations are all contiguous values, iteration over the entire set of enumeration is also possible. Strictly speaking, iteration is not a part of the idiom, but it is a useful enhancement. Following code shows the enhancements needed to add iteration capability. A static array of safe_enums is created for each instantiation of safe_enum class. The array is populated by the initialize function with the original enumeration values. Note that enumeration values are assumed to be contiguous in the initialize function. Once the array is initialized, iteration is simply traversal from beginning to the end of the static array. The begin and end functions return iterators to the beginning and (one past) the end of the array.

template<typename def, typename inner = typename def::type>
class safe_enum : public def
{
  // ... 
  // The stuff shown above goes here.
  // ...
private:
  static safe_enum array[def::_end_ - def::_begin_];
  static bool init;

  // Works only if enumerations are contiguous.
  static void initialize()
  {
    if(!init) // use double checked locking in case of multi-threading.
    {
      unsigned int size = def::_end_ - def::_begin_;
      for(unsigned int i = 0, j = def::_begin_; i < size; ++i, ++j)
        array[i] = static_cast<typename def::type>(j);
      init = true;
    }
  }

public:
  static safe_enum * begin() {
    initialize();
    return array;
  }

  static safe_enum * end() {
    initialize();
    return array + (def::_end_ - def::_begin_);
  }
};

template <typename def, typename inner>
safe_enum<def, inner> safe_enum<def, inner>::array[def::_end_ - def::_begin_];

template <typename def, typename inner>
bool safe_enum<def, inner>::init = false;

template <class Enum>
void f(Enum e)
{
  // ...
}
int main()
{
  typedef safe_enum<color_def, unsigned char> color;
  std::for_each(color::begin(), color::end(), &f<color>);
}

Known Uses[edit | edit source]

Related Idioms[edit | edit source]


References[edit | edit source]