Iterating over an enum

The enum feature of C/C++ is far from new. It is very useful for defining a specific set of values that a simple integer may take, which can lead to clearer, more concise code when used appropriately. Many compilers are capable of warning about common errors associated with enum use, such as not including case statements for all possible enum values in a switch statement that has no default clause. In many respects, an enum acts like a set, but being essentially just a glorified int, it lacks any of the container features of something like std::set.

The developer typically faces a tradeoff between performance and functionality when deciding between an enum or some kind of set-like container. There are some (often common) situations, however, where an enum can still be treated like a container, thanks to features made available in C++11.

An enum without assigned values

For developers coming from more of a C background or C++ developers who are not so familiar with the STL, it is not unusual to encounter code such as the following:

enum Shape
{
    Circle,
    Square,
    Triangle,
    Oval,
    Polygon,
    NumShapes
};

// Variant 1
for(Shape s=(Shape)0; s!=NumShapes; s=(Shape)(s+1))
{
    // ...
}

// Variant 2
for(int i=0; i!=NumShapes; ++i)
{
    Shape s = (Shape)i;
    // ...
}

This sort of use of enum is not ideal. It includes a count in the list of Shape values, which reduces the usefulness of the enum in defining all meaningful shapes because a Shape object can hold a value which is not an actual valid shape at all. This diminishes one of the main advantages of using an enum, which is to define precisely the set of allowable values and write code which only needs to consider those values as possibilities. Another disadvantage of this sort of approach is that it requires casting between a raw int and Shape, which reduces the robustness of the code. A slightly better alternative that is sometimes used is to store the count outside of the enum:

enum Shape
{
    Circle,
    Square,
    Triangle,
    Oval,
    Polygon
};
const int NumShapes = 5;

In this case, Shape specifies only allowable values, which is good, but the separate counter is a fragile way to track the number of defined values. If the enum grows large, it would be easy for a developer to forget to update the NumShapes value and tracking down this bug could be quite difficult. This approach also does nothing to help us avoid the undesirable cast between int and Shape when iterating over the set of enumerated values.

It would be better to dispense with the counter altogether and treat the enum more like a container, using iterators or ranges (the latter being a C++11 feature). Conveniently, std::initializer_list (see previous article) allows us to do exactly that, with a little bit of help from a C pre-processor feature called variadic macros. Variadic macros are the macro equivalent of variadic arguments which most programmers know from using printf() and friends. Simply place ... at the end of your macro’s argument list and then use __VA_ARGS__ wherever you want the list of remaining arguments to appear.

#define SequentialEnum(Name,...) 
    enum Name { __VA_ARGS__ }; 
    namespace 
    { 
        const std::initializer_list<Name> Name##List { __VA_ARGS__ }; 
    };

SequentialEnum(Shape,
    Circle,
    Square,
    Triangle,
    Oval,
    Polygon
);

for (Shape s : ShapeList)
{
    // loop body...
}

The SequentialEnum macro conveniently allows the developer to define the set of enum values once and to be able to iterate over them like a container. This is both concise and robust, without sacrificing readability.

An enum with assigned values

All of the above examples share the common assumption that the enum values have the default numbering. This applies to many of the common uses of an enum, but for an enum which has any explicitly numbered values, none of the above strategies will work. Unfortunately, there is no easy way to automate creating some kind of initializer list when at least one of the enumerants is assigned a value. The method we used previously relied on the format of an enum list being the same as that for an initializer list. When enumerants are assigned a value, the only clean alternative is to manually specify an appropriate initializer list explicitly.

enum Shape
{
    Circle   = 23,
    Square   = 17,
    Triangle = 92,
    Oval     =  4,
    Polygon  = 15
};
static const auto ShapeList
{
    Circle,
    Square,
    Triangle,
    Oval,
    Polygon
};
static const auto CurvedShapes{ Circle, Oval };
static const auto StraightShapes{ Square, Triangle, Polygon };

We’ve used auto as a convenience instead of explicitly specifying the std::initializer_list type. Whether you use a static variable or an anonymous namespace is also largely a matter of personal choice.

The above has the obvious drawback that the list of enumerants is specified twice, which is arguably no better than when we manually specified the number of enum values near the beginning of this article. One thing it does still do, however, is allow the enum to be treated like a container and iterate over the defined values in a very clean and concise way. For enums with only a small number of values, this may be an acceptable tradeoff. As shown above, we can also use this same technique to define subsets of the enum values which can then be referred to consistently across the code base.

Closing comments

C++11’s ranges and std::initializer_list in particular are very useful tools which enable convenient iteration over enum values. Their ability to build containers of values at compile time provide many of the advantages of a set-like container without having to pay the performance tradeoffs.

One thought on “Iterating over an enum

  1. Pingback: C++ List Initialization | Crascit

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s