Enum: Enumerations

Enumerations are used to define sets of integer values and for defining types for such sets of values. There are two kind of enumerations, "plain" enums and class enums.

Enumeration rule summary:

Enum.1: Prefer enumerations over macros

Reason

Macros do not obey scope and type rules. Also, macro names are removed during preprocessing and so usually don't appear in tools like debuggers.

Example

First some bad old code:

// webcolors.h (third party header)
#define RED 0xFF0000
#define GREEN 0x00FF00
#define BLUE 0x0000FF
// productinfo.h
// The following define product subtypes based on color
#define RED 0
#define PURPLE 1
#define BLUE 2
int webby = BLUE; // webby == 2; probably not what was desired

Instead use an enum:

enum class Web_color { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF };
enum class Product_info { red = 0, purple = 1, blue = 2 };
int webby = blue; // error: be specific
Web_color webby = Web_color::blue;

We used an enum class to avoid name clashes.

Enforcement

Flag macros that define integer values.

Enum.2: Use enumerations to represent sets of related named constants

Reason

An enumeration shows the enumerators to be related and can be a named type.

Example
enum class Web_color { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF };
Note

Switching on an enumeration is common and the compiler can warn against unusual patterns of case labels. For example:

enum class Product_info { red = 0, purple = 1, blue = 2 };
void print(Product_info inf)
{
switch (inf) {
case Product_info::red: cout << "red"; break;
case Product_info::purple: cout << "purple"; break;
}
}

Such off-by-one switch-statements are often the results of an added enumerator and insufficient testing.

Enforcement
  • Flag switch-statements where the cases cover most but not all enumerators of an enumeration.
  • Flag switch-statements where the cases cover a few enumerators of an enumeration, but there is no default.

Enum.3: Prefer class enums over "plain" enums

Reason

To minimize surprises: traditional enums convert to int too readily.

Example
void Print_color(int color);
enum Web_color { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF };
enum Product_info { red = 0, purple = 1, blue = 2 };
Web_color webby = Web_color::blue;
// Clearly at least one of these calls is buggy.
Print_color(webby);
Print_color(Product_info::blue);

Instead use an enum class:

void Print_color(int color);
enum class Web_color { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF };
enum class Product_info { red = 0, purple = 1, blue = 2 };
Web_color webby = Web_color::blue;
Print_color(webby); // Error: cannot convert Web_color to int.
Print_color(Product_info::red); // Error: cannot convert Product_info to int.
Enforcement

(Simple) Warn on any non-class enum definition.

Enum.4: Define operations on enumerations for safe and simple use

Reason

Convenience of use and avoidance of errors.

Example
enum Day { mon, tue, wed, thu, fri, sat, sun };
Day& operator++(Day& d)
{
return d = (d == Day::sun) ? Day::mon : static_cast<Day>(static_cast<int>(d)+1);
}
Day today = Day::sat;
Day tomorrow = ++today;

The use of a static_cast is not pretty, but

Day& operator++(Day& d)
{
return d = (d == Day::sun) ? Day::mon : Day{++d}; // error
}

is an infinite recursion, and writing it without a cast, using a switch on all cases is long-winded.

Enforcement

Flag repeated expressions cast back into an enumeration.

Enum.5: Don't use ALL_CAPS for enumerators

Reason

Avoid clashes with macros.

Example, bad
// webcolors.h (third party header)
#define RED 0xFF0000
#define GREEN 0x00FF00
#define BLUE 0x0000FF
// productinfo.h
// The following define product subtypes based on color
enum class Product_info { RED, PURPLE, BLUE }; // syntax error
Enforcement

Flag ALL_CAPS enumerators.

Enum.6: Avoid unnamed enumerations

Reason

If you can't name an enumeration, the values are not related

Example, bad
enum { red = 0xFF0000, scale = 4, is_signed = 1 };

Such code is not uncommon in code written before there were convenient alternative ways of specifying integer constants.

Alternative

Use constexpr values instead. For example:

constexpr int red = 0xFF0000;
constexpr short scale = 4;
constexpr bool is_signed = true;
Enforcement

Flag unnamed enumerations.

Enum.7: Specify the underlying type of an enumeration only when necessary

Reason

The default is the easiest to read and write. int is the default integer type. int is compatible with C enums.

Example
enum class Direction : char { n, s, e, w,
ne, nw, se, sw }; // underlying type saves space
enum class Web_color : int32_t { red = 0xFF0000,
green = 0x00FF00,
blue = 0x0000FF }; // underlying type is redundant
Note

Specifying the underlying type is necessary in forward declarations of enumerations:

enum Flags : char;
void f(Flags);
// ....
enum Flags : char { /* ... */ };
Enforcement

????

Enum.8: Specify enumerator values only when necessary

Reason

It's the simplest. It avoids duplicate enumerator values. The default gives a consecutive set of values that is good for switch-statement implementations.

Example
enum class Col1 { red, yellow, blue };
enum class Col2 { red = 1, yellow = 2, blue = 2 }; // typo
enum class Month { jan = 1, feb, mar, apr, may, jun,
jul, august, sep, oct, nov, dec }; // starting with 1 is conventional
enum class Base_flag { dec = 1, oct = dec << 1, hex = dec << 2 }; // set of bits

Specifying values is necessary to match conventional values (e.g., Month) and where consecutive values are undesirable (e.g., to get separate bits as in Base_flag).

Enforcement
  • Flag duplicate enumerator values
  • Flag explicitly specified all-consecutive enumerator values