Advertisement

Low-calorie enumerations

October 16, 2008

Dan_Saks-October 16, 2008

Enumeration types offer elegant solutions to many programming problems. Unfortunately, they pose problems for developers who need to control the amount of storage that objects use. As it stands, neither C nor C++ offers a standard way to control the size of an enumeration type. This month, I look at the available alternatives for controlling the storage allocated for enumeration objects and explain how the solution proposed in the new draft standard for C++ is the best one yet.

A short while back, I explained that C++ treats enumeration types differently from the way C does.1 In C, each enumeration constant has type int and each enumeration type is compatible with type char, or some signed or unsigned integer type. In C++, each enumeration is a distinct type.

For compatibility with C, C++ permits implicit conversions from enumeration types to int, but not from integer types to enumeration types or from one enumeration type to another. The draft C++ standard provides an even more strongly typed enumeration. An enum class defined as:

enum class month { Jan, Feb, Mar, ..., Dec };

disallows implicit conversions from month to int.

The C++ Standard refers to the type used to represent a given enumeration as that enumeration's underlying type.2 Currently, neither Standard C nor Standard C++ lets you specify the type you'd like to use to represent a particular enumeration. For example, all values for an enumeration type such as:

enum month { Jan, Feb, Mar, ..., Dec };
typedef enum month month;

can easily fit into a single char. However, there's no standard way to force a compiler to represent the enumeration type month as only a byte. Some compilers offer command options or #pragma directives for this, but these are non-standard.

If you want to represent a month as a byte, you can write something like this:

enum month { Jan, Feb, Mar, ..., Dec };
typedef unsigned char month;

These declarations are valid in C and still let you treat type month just as you would if it were an enumeration type. For example:

month m;
for (m = Jan; m <= Dec; ++m)
    ...

compiles and runs whether month is a genuine enumeration or a typedef.

As I explained some years ago, C lets you declare a tag name (enum, struct, or union name) with the same spelling as a non-tag name (constant, function, object, or type name) in the same scope.3 In the case of:

enum month { Jan, Feb, Mar, ..., Dec };
typedef unsigned char month;

both the tag name enum month and the non-tag name month are type names, and they name different types. While sizeof(month) is guaranteed to be one, sizeof(enum month) could well be something larger. Using declarations such as these is probably asking for trouble--trouble you won't get in C++. C++ won't let you declare a non-tag name as a type if a tag name with the same spelling is already declared in the same scope.

The declarations will compile in both C and C++ if you omit the tag name month from the enumeration definition, as in:

enum { Jan, Feb, Mar, ..., Dec };
typedef unsigned char month;

This works reasonably well in C, but you have to be sure that the integer type you use in the typedef is big enough to hold all of your enumeration values.

Using enumerations defined in this style sacrifices type checking in C++. For example, if you declare enumerations day and month in the usual way:

enum day { Sun, Mon, Tue, ..., Sat };
enum month { Jan, Feb, Mar, ..., Dec };

then the assignment in:

day d;
month m;
...
m = d;      // compile error

won't compile. On the other hand, if you declare the types as:

typedef unsigned char day;
typedef unsigned char month;

then the assignment in:

day d;
month m;
...
m = d;      // OK?

compiles without complaint. This isn't good.

You also lose the ability to overload functions and operators for each enumeration type that's actually a typedef. For example, when day is a true enumeration type, you can overload the << operator so that:

day d = Tue;
...
std::cout << d;

displays the text Tue rather the integer 2. You can't do this when day is a typedef alias for some integer type.

Enum classes in the new draft standard for C++ offer a nice resolution to this conflict: you can explicitly specify the underlying type for each enum class. For example:

enum class month: unsigned char { Jan, Feb, Mar, ..., Dec };

specifies that objects of enumeration type month will be represented as unsigned char. The compiler will verify that the underlying type can represent each enumeration value and complain if it can't.

As an enum class, type month is still distinct from all other types. You can't convert a month into an unsigned char or an int or any other enumeration type unless you use a cast. You can overload functions and operators on type month. All of this is good, or at least it will be, once it becomes available.

Endnotes:
1. Saks, Dan, "Enumerations are integers, except when they're not", Embedded.com, September 2008, www.embedded.com/210101371.
Back

2. ISO/IEC 14882:2003(E), Programming Languages--C++, 2nd edition.ISO catalog.
Back

3. Saks, Dan, "Tag vs. Type Names", Embedded Systems Programming, October 2002. www.embedded.com/columns/programmingpointers/9900748
Back

Loading comments...