Tuesday, June 3, 2008

Enums in C++ Suck

Like most strongly typed languages, C++ has a way to group a set of constants together as their own type called enums. Enums are extremely useful in a wide variety of circumstances. However, enums in C++ have a lot of problems, and, in fact, they're really a mess. I'm certainly not the only person to complain about this, either.

Enums don't fit in with the rest of the language. They feel like something that was tacked onto the language to me. This is purely an aesthetic issue, and the fact that they're useful in a wide variety of circumstances probably negates this.

More practically, you can't control the conversion of the enum to and from integers. For example, you can use the less than operator to compare an enum and an integer without using a cast. This can result in accidental conversions that don't make sense.

Perhaps the worst problem is the scope of the constants defined by the enum. They are enclosed in the same scope as the enum itself. I've seen a lot of code where people prepend an abbreviation of the enum's type to each of the enum's constants to avoid this problem. Adding the type to the name of a constant is always a good sign that something bad is happening.

In addition, you can't decide ahead of time what the size of your enum's type is. C++ normally tries to give the programmer as much control as possible. In the case of enums, this allows the compiler to store your enum in whatever type it wants to. Frequently, this doesn't matter, but when it does matter, you'll end up copying the value into an integer type that's less expressive than than the enum.

After the break, I'll explain what other languages are doing about it, what the next iteration of the C++ standard will do about it, and what you can do about it now.
I'll use a simple example for this dicussion:

enum Shape {
Circle, Triangle, Square
};
bool shouldBeAnError = Circle < 0;

C# has enums that behave a lot closer to what I'd like. This is what they look like:

enum Shape {
Circle, Triangle, Square
}
// Note the wonderfully ugly conversion and the need to explicitly
// say that circles are shapes.
bool isALotMoreExplicit = Shape.Circle < (Shape)0;

I'm not the first person to notice this problem, obviously. The next iteration of C++, C++0x, is going to add a much safer enum. This is what it'll look like:

enum class Shape
: int
{
Circle, Triangle, Square
};
// Note that we have to explicitly say that Circle is a Shape. That's great.
// The current standards document doesn't say how I can convert an int
// to the enum, though. I'll see if I can post a comment on that...
bool isALotMoreExplicit = Shape::Circle < (Shape)0;

It's actually not so hard to do a lot of that yourself if you throw out idea of using enums. For example, at home, I use something that looks a bit like this:

DECLARE_ENUM( Shape, int, (Circle)(Triangle)(Square) );
bool isAsExplicitAsIWant = Shape::Circle < Shape(0);

With only a bit of preprocessor magic, you can do the same thing.

Here at PC-Doctor, we define our enums in an XML file and use a code generation step to create the enum. The end result is the same as the preprocessor method: the enum can do whatever you want it to do.

While standard C++ enums are relics of C, lack most of the safety that C++ programmers are used to, and have a variety of other problems, there are ways around the problem. C++0x will add another alternative, but it lacks the flexibility of a home grown solution. You may decide to stick with your own solution even after your compiler starts supporting the new enums.

This post was originally published on the PC-Doctor blog.

No comments: