c++boost-logo   Header <boost/smart_enum.hpp>

Abstract

This library provides a generic wrapper template for enum data types, allowing for checked maniplation of enum instances. In particular, the template classes can verify arithmetic and assignment expressions at compile-time and run-time with neglectable overhead. All functionality is provided in the header file; there is no need to link any binary object files to your application in order to use this library.

Please note that — despite the look and feel of the library and its documentation — smart_enum is not officially part of the Boost library effort, even though I (obviously) plan to submit it eventually.


Contents


Motivation for smart_enum

Program writers frequently have to store state information in their classes, which determines the next step in the program flow. Another frequent requirement is to denote certain distinct values, which are grouped by some relation, using descriptive names that suggest the value's meaning. The intuitive representation for these applications is enum, which has been designed exactly for this purpose.

Unfortunately, enum has a few shortcomings:

  1. An enum is always instantiated uninitialized. There's no way to add a default constructor, because enum is a fundamental datatype.
  2. For the same reason, you cannot derive from enum in order to add the required member functions to the derived class.
  3. If you want to use an enum in an arithmetic expression, such as
    ++state
    state = BASE_STATE + 2
    state = state + 5
    state = 16
    you would have to define a whole bunch operators to get things checked at run-time. And if you don't want to do that, you'll have to use some sort of cast to get the expressions to compile at all because the compiler will implicitely convert the enum to int in order to do the maths. But apparently, it cannot convert the result of the integer expression back to enum.

Solving this dilemma is not a hard problem, but it is an annoying problem. You should rather concentrate on your application than to worry about missing operators.


Overview of the components provided by the library

smart_enum aims to make your life easier by providing a generic framework, which turns an enum into a simple class that can be incremented, decremented, used in complex arithmetic expressions, and be assigned with arbitrary values. Yet, the class enforces that the result of these expressions will always a valid value of the underlying enum type.

This objective is achieved by splitting the functionality into two components: An so called Incrementor and a generic smart_enum template, which uses a given Incrementor to manipulate its value. This relationship is illustrated in the following diagram:

What makes a smart_enumUML diagram of smart_enum's components

To understand how this construct works, let us take a look at the Incrementor first. An Incrementor is any function object, that provides the member function:

enumT operator() (int val, int n)

This function accepts an arbitrary value val, which is supposed to be a valid value of enumT, and the integer value n, by which val should be incremented. If this expression yields a valid enumT value, the Incrementor will return it. Otherwise, the Incrementor may throw an exception, fail with an assertion, or simply return a default value instead.

One of the standard Incrementors provided by the library is SequentialIncrementor, which asserts that the resulting value is in the range [minVal, maxVal]. It is defined as follows:

template<typename enumT, enumT minVal, enumT maxVal>
struct SequentialIncrementor
        : public std::binary_function<int, int, enumT>
    {
    enumT operator() (int val, int n) const
        {
        if (val + n >= minVal && val + n <= maxVal)
            return enumT(val + n);
        else
            throw std::out_of_range("invalid enum");
        }
    };

Using this Incrementor combined with the smart_enum template, you can turn an enum into a range-checked smart_enum as follows:

enum myEnum { state1, state2, state3, state4 };
typedef smart_enum<
                  myEnum,
                  SequentialIncrementor<myEnum, state1, state4>
                  > my_enum_t;
my_enum_t e(state1);

Of course nobody wants to write code like this, so to make your life a bit easier, specialized versions of smart_enum are included in the library, which provide a nicer interface. The example code shown above, could also be written as:

typedef sequential_smart_enum<myEnum, state1, state4> my_enum_t;

This is achieved by providing a short helper-class, which is hard-coded to use SequentialIncrementor. The architecture of this class is illustrated in the following diagram:

Combining smart_enum with a specific IncrementorUML diagram of
        sequential_smart_enum

The library provides three standard Incrementors plus appropriate helper classes to simlify the interface. These Incrementors are discussed in the following sections. Those, who wish to write Incrementors of their own, should read section Using template smart_enum directly instead.


Template sequential_smart_enum

The template sequential_smart_enum ties a generic smart_enum to a SequentialIncrementor instance, which is instantiated with the appropriate boundaries. The class must be instantiated with the following template parameters:

sequential_smart_enum<typename enumT, enumT minVal, enumT maxVal>

enumT is — obviously — the type of the enum you'd like to wrap into a smart_enum class, and minVal and maxVal specify the range of valid values for this type of enum. Here is a concrete example:

enum foo { state1, state2, state3 };
sequential_smart_enum<foo, state1, state3> mystate(state1);

The resulting mystate instance behaves like the underlying enum does, but you can use it as part of an arithmetic expression, increment, decrement, or assign it. Every time you do, sequential_smart_enum will make sure its value is in the range of [minValmaxVal]. If an operation on the instance would result in an invalid value, a std::out_of_range exception will be thrown:

my_foo_state = 0;      // state1
++my_foo_state;        // state2
my_foo_state++;        // state3
my_foo_state -= 2;     // state1

my_foo_state = -5;     // throws std::out_of_range
(my_foo_state = 0)--;  // throws std::out_of_range

The only significant difference between sequential_smart_enum and a plain enum is that you cannot instantiate an uninitialized sequential_smart_enum; the template does not provide a default constructor.

Please note that sequential_smart_enum cannot validate enums that have gaps in their values, such as:

enum not_sequential { first = 1, second = 3 };

If you need to represent such an enum, you'll have to write an appropriate Incrementor of your own. More on this topic can be found in section Using template smart_enum directly.


Template wrapped_smart_enum

The template wrapped_smart_enum is to be used exactly like a sequential_smart_enum and has the same properties — with one exception: The class is circular. If the value leaves the range [minValmaxVal] at one end, it will be set to the other end. The class must be instantiated with the following template parameters:

wrapped_smart_enum<typename enumT, enumT minVal, enumT maxVal>

enumT is — obviously — the type of the enum you'd like to wrap into a smart_enum class, and minVal and maxVal specify the range of valid values for this type of enum. Here is a concrete example:

enum foo { state1, state2, state3 };
wrapped_smart_enum<foo, state1, state3> mystate(state1);

++my_foo_state;        // state2
++my_foo_state;        // state3
++my_foo_state;        // state1 (!)

Because of its semantics, wrapped_smart_enum won't fail nor throw any exceptions. Like sequential_smart_enum, though, it cannot validate enums that have gaps in their values!


Template bounded_smart_enum

The template bounded_smart_enum is to be used exactly like a sequential_smart_enum and has the same properties — with one exception: The class won't let its value leave the range [minValmaxVal] by substituting minVal for any value for any value that is smaller than minVal and maxVal for any value that is larger than maxVal. It must be instantiated with the following template parameters:

bounded_smart_enum<typename enumT, enumT minVal, enumT maxVal>

enumT is — obviously — the type of the enum you'd like to wrap into a smart_enum class, and minVal and maxVal specify the range of valid values for this type of enum. Here is a concrete example:

enum foo { state1, state2, state3 };
bounded_smart_enum<foo, state1, state3> mystate(state1);

my_foo_state = -50;    // state1 (minVal)
my_foo_state = 50;     // state3 (maxVal)
++my_foo_state;        // still state3
my_foo_state += 100;   // and still state3

This property is achived by using the modulus operator; hence it is possible to assign arbitrary value to the instance and still get a (hopefully) meaningful result:

my_foo_state = 50;     // 50 % 3 = 2 --> state3

Consequently, bounded_smart_enum won't fail nor throw any exceptions. Like sequential_smart_enum, though, it cannot validate enums that have gaps in their values.


Using template smart_enum directly

[to be written once the class is really stable]


Sheer madness with boost::lambda

If you are really adventureous, don't fear discovering esotheric compiler bugs, and always wanted to use template meta-programming, you can combine the boost::lambda library with smart_enum. Just set the Incrementor to be used when instantiating the smart_enum class, then you can use a lambda function to write the code on-the-fly:

Really weird stuff …
using namespace boost::lambda;    // Be afraid, be very afraid!

enum myEnum { north = 17, east = 4, south = 92, west = -5 };

typedef smart_enum<
                  myEnum,
                  boost::function<myEnum, int, int>
                  > my_enum_t;
my_enum_t e(north, (
    // Verify that _1 is a valid myEnum.

    if_then(_1 != north && _1 != east && _1 != south && _1 != west,
            throw_exception(std::out_of_range("invalid myEnum!"))),

    // Our enum is wrapped, what allows for two nice short-cuts.

    _2 = _2 % 4,
    if_then(_2 < 0, _2 = 4 + _2),

    // Now increment appropriately.

    while_loop(--_2 >= 0, switch_statement(
                   _1,
                   case_statement<north>(_1 = east),
                   case_statement<east> (_1 = south),
                   case_statement<south>(_1 = west),
                   case_statement<west> (_1 = north))),

    // Return result.

    ll_static_cast<myEnum>(_1)));

assert(e == north);
assert(++e == east);
assert(++e == south);
assert(++e == west);
assert(++e == north);
assert(++e == east);

This example almost exactly duplicates the functionality of a wrapped_smart_enum, but unlike its simpler (and more generic) counterpart, this version can cope with an enum that is not sequential. Take a look at the test_lambda_enum.cpp regression test included in this library, if you want to see a full example program for this technique.


Downloading the library

The shapshot tarball smart-enum-HEAD.tar.gz is available for download. Further information is available in the git repository.


License

Copyright © 2002-2007 by Peter Simons. Permission to copy, use, modify, sell and distribute this document is granted provided this copyright notice appears in all copies. This document is provided "as is" without express or implied warranty, and with no claim as to its suitability for any purpose.