Channels ▼
RSS

When enum Just Isn't Enough: Enumeration Classes for C++


Listing 4 gives an example of how the "enumeration class" may be used in a simple test driver. This code was compiled and run on Metrowerks CodeWarrior 7.

Listing 4

Test_Enum.h

class Test_Enum: public Enum<Test_Enum> {

private:
 explicit Test_Enum(int Value): Enum<Test_Enum>(Value) { }

public:
  static const Test_Enum enum_11;
  static const Test_Enum enum_12;
  static const Test_Enum enum_18;
  static const Test_Enum enum_20;
  static const Test_Enum enum_21;
};

Test_Enum.cpp

#include "Test_Enum.h"

Enum<Test_Enum>::instances_list Enum<Test_Enum>::s_instances;

const Test_Enum Test_Enum::enum_11(11);
const Test_Enum Test_Enum::enum_12(12);
const Test_Enum Test_Enum::enum_18(18);
const Test_Enum Test_Enum::enum_20(20);
const Test_Enum Test_Enum::enum_21(21);

Main.cpp

#include "Test_Enum.h"
int main()
{
  using std::cout;
  using std::endl;<span style='mso-tab-count:1'> </b>
  int Cur_Elem = 0;
  for ( Test_Enum::const_iterator i = Test_Enum::begin();
        i != Test_Enum::end(); ++i )
  {
   Cur_Elem++;
   cout << "Test_Enum element #" << Cur_Elem << " value = "
   << (*i)->Get_Value() << endl;
  }

  cout << "Total #elements = " << Test_Enum::size() << endl;
  cout << "Min enum value = " << Test_Enum::Min() << endl;
  cout << "Max enum value = " << Test_Enum::Max() << endl;

  for ( int i = Test_Enum::Min(); i <= Test_Enum::Max(); ++i )
  {
   cout << i;
   if ( Test_Enum::Is_Valid_Value(i) ) cout << " is ";
   else cout << " isn't ";
   cout << "a valid value for Test_Enum." << endl;
  }

  Test_Enum E(Test_Enum::enum_11);
  cout << "Value of E = " << E.Get_Value() << endl;

  E = Test_Enum::enum_20;
  cout << "Value of E = " << E.Get_Value() << endl;

  // Illegal code
  // bool B = E; // won't compile, illegal implicit conversion
  // E++; // won't compile, cannot treat Test_Enum as an int
  // Test_Enum X(17); // won't compile, ctor is private

  return 0;
}

Output:

Test_Enum element #1 value = 11
Test_Enum element #2 value = 12
Test_Enum element #3 value = 18
Test_Enum element #4 value = 20
Test_Enum element #5 value = 21
Total #elements = 5
Min enum value = 11
Max enum value = 21
11 is a valid value for Test_Enum.
12 is a valid value for Test_Enum.
13 isn't a valid value for Test_Enum.
14 isn't a valid value for Test_Enum.
15 isn't a valid value for Test_Enum.
16 isn't a valid value for Test_Enum.
17 isn't a valid value for Test_Enum.
18 is a valid value for Test_Enum.
19 isn't a valid value for Test_Enum.
20 is a valid value for Test_Enum.
21 is a valid value for Test_Enum.
Value of E = 11
Value of E = 20

Advantages and Drawbacks

Enumeration classes have the following advantages over enums: You can programmatically access their minimum and maximum values, test whether a given int is a valid enumeration constant, and iterate over a given enumeration class. An enumeration class instance is never implicitly converted to an int value, although this value may be obtained explicitly. Given an int value, you can obtain the corresponding enumeration class constant, if it exists.

Enumeration classes do have some drawbacks: for one, they are more verbose to define (but not to use) than enums. For another, they cannot be used directly in a switch statement, although you can of course switch using the Get_Value function. Even though these are not major handicaps, the fact remains that using enumeration classes is less straightforward than using ordinary enums.

Keep in mind, however, that enumeration classes are not meant to blindly replace all enums in existing code, but rather to replace those that correspond to collections of related constants rather than symbolic representations of unified abstract concepts. In other words, if you're using an enum to group together the numbers of all the error messages your application uses, you should consider an enumeration class instead; but a days-of-the-week enum should remain a plain enum.

Generalization: Why Limit Yourself To ints?

What if you refer to resources using something more sophisticated than a dumb int? What use is an enumeration class when your sound-playing code reads PlaySound("Phaser1");? Well, in fact, an enumeration class is just what you need. The basic ideas I've presented here apply just as well when the underlying enumerated type is something other than int — as long as it can be ordered (i.e., it has or can have an operator<). The benefits can be even greater, since an enumeration class always allows iteration over the underlying type even when this is impossible in the general case. In practice, this means an enumeration class enables you to iterate over every sound-resource string in your application, from "Arrakis" to "Zabulon Computation."

A generalized enumeration class is templated on its underlying type. Listing 5 provides such a class.

Listing 5

#include <functional>
#include <set>

template <class TValue, class T>
class Tmpl_Enum {

private:

 // Constructors
 explicit Tmpl_Enum(const TValue& Value);

 // Predicate for finding the corresponding instance
 struct Enum_Predicate_Corresponds:
    public std::unary_function<const Tmpl_Enum<TValue, T>*, bool> {

      Enum_Predicate_Corresponds(const TValue& Value): m_value(Value) { 
}
      bool operator()(const Tmpl_Enum<TValue, T>* E)
      { return E->Get_Value() == m_value; }

    private:
      const TValue& m_value;
 };

 // Comparison functor for the set of instances
 struct Enum_Ptr_Less:
    public std::binary_function<const Tmpl_Enum<TValue, T>*, const Tmpl_Enum<TValue, T>*, bool> {
      bool operator()(const Tmpl_Enum<TValue, T>* E_1, const Tmpl_Enum<TValue, T>* E_2)
      { return E_1->Get_Value() < E_2->Get_Value(); }
 };

public:
 // Compiler-generated copy constructor and operator= are OK.

 typedef std::set<const Tmpl_Enum<TValue, T>*, Enum_Ptr_Less> instances_list;
 typedef instances_list::const_iterator const_iterator;

 // Access to TValue value
 const TValue& Get_Value(void) const { return m_value; }
 static const TValue& Min(void) { return (*s_instances.begin())->m_value; }
 static const TValue& Max(void) { return (*s_instances.rbegin())->m_value; }
 static const Tmpl_Enum<TValue, T>* Corresponding_Enum(const TValue& Value)
 { const_iterator it = find_if(s_instances.begin(), s_instances.end(), Enum_Predicate_Corresponds(Value));
    return (it != s_instances.end()) ? *it : NULL; }
 static bool Is_Valid_Value(const TValue& Value) { return Corresponding_Enum(Value) != NULL; }

 // Number of elements
 static instances_list::size_type size(void) { return s_instances.size(); }

 // Iteration
 static const_iterator begin(void) { return s_instances.begin(); }
 static const_iterator end(void) { return s_instances.end(); }

private:
 TValue m_value;

 static instances_list s_instances;
};


template <class TValue, class T>
inline Tmpl_Enum<TValue, T>::Tmpl_Enum(const TValue& Value):
 m_value(Value)
{
 s_instances.insert(this);
}


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video