Strongly Typed Enumerations in C++ - dummies

By Stephen R. Davis

Programming is all about legibility. It’s difficult (actually it’s impossible) to write and maintain a program that you can’t read. Part of reading a source-code listing is understanding what the numbers used in the program represent. The most basic aid that C++ provides is the ubiquitous #define, as in the following oft-cited example:

#define PI 3.141592653589793

This solution is okay for individual values, though it suffers from the fact that the #define mechanism is not (strictly speaking) a part of C/C++ since the preprocessor runs before the compiler. In response to that, C++ 2011 introduced a constant expression construct:

constexpr long double PI = 3.141592653589793;

The constexpr keyword brings constants into the C++ tent. This PI has a real type, like other C++ variables. C++ can generate error messages with PI that make a lot more sense than those involving 3.14159.

Constant expressions are fine for individual constant values, but often constants represent sets of things rather than natural constants, as in the following example:

#define ALABAMA  1
#define ALASKA   2
#define ARKANSAS 3
// ...and so on...

Presumably these constants are being used to identify the states, perhaps being used as an index into an array of state objects or as a value in a database somewhere.

C++ has long had an improved mechanism for defining these types of constants — the enumeration:

enum STATE {DC_OR_TERRITORY,  // gets 0
            ALABAMA,          // gets 1
            ALASKA,           // gets 2
           // ...and so on...

The enum keyword introduces a sequence of constants called an “enumeration”. In this case, the enumeration carries the name STATE. Each element of this enumeration is assigned a value starting at 0 and increasing sequentially by 1, so DC_OR_TERRITORY is defined as 0, ALABAMA is defined as 1, and so on. You can override this incremental sequencing by using an assign statement as follows:

enum STATE {DC,
            TERRITORY = 0,
            // ...and so on...

This version of STATE defines an element DC, which is given the value 0. It then defines a new element TERRITORY, which is also assigned the value 0. ALABAMA picks up with 1, just as before.

In practice, the programmer can use enumerations to write quite readable code like the following:

double taxRate(STATE s)
    return taxRatesByState[s];

The only problem with this approach is that this enumeration doesn’t create a new type (as you might think). In fact, according to the standard, STATE is just another name for int — and the constants ALABAMA, ALASKA, and so on are all of type const int.

The gcc compiler actually does provide an enum declared in this way a little bit more authority than simply calling it another form of int. You can actually overload functions based on an enum type:

void fn(STATE s);
void fn(int n);
fn(ALASKA);  // invokes fn(STATE)

The 2011 standard allows the programmer to create a completely new type using the enum keyword. Since the creators of the new standard didn’t want to break existing code, the standard requires the addition of an extra keyword in order to define an enumeration type, as in the following example:

enum class STATE {DC,
                  TERRITORY = 0,
                  // ...and so on...

An enumeration class is now a full-scale type like any other user-defined class. The following isn’t even legal anymore for two reasons:

int s = ALASKA;

First, the constant ALASKA is only defined within the STATE namespace. Thus, the name of the constant is STATE::ALASKA. Second, the type is not int but STATE. You can’t assign a value of type STATE to an int.


The programmer can recast a STATE into an int but she must do so explicitly —implicit conversions don’t cut it with enumeration classes:

int n = (int)STATE::ALASKA;

This new enum type can also be based upon one of the other counting number types besides just int:

enum class STATE : char {DC,
// ...the remainder of the declaration is same