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" enum
s and class enum
s.
Enumeration rule summary:
- (Enum.1: Prefer enumerations over macros)
- (Enum.2: Use enumerations to represent sets of related named constants)
- (Enum.3: Prefer
enum class
es over "plain"enum
s) - (Enum.4: Define operations on enumerations for safe and simple use)
- (Enum.5: Don't use
ALL_CAPS
for enumerators) - (Enum.6: Avoid unnamed enumerations)
- (Enum.7: Specify the underlying type of an enumeration only when necessary)
- (Enum.8: Specify enumerator values only when necessary)
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 2int 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 specificWeb_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 thecase
s cover most but not all enumerators of an enumeration. - Flag
switch
-statements where thecase
s cover a few enumerators of an enumeration, but there is nodefault
.
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 colorenum 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 enum
s.
Example
enum class Direction : char { n, s, e, w, ne, nw, se, sw }; // underlying type saves spaceenum 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 }; // typoenum class Month { jan = 1, feb, mar, apr, may, jun, jul, august, sep, oct, nov, dec }; // starting with 1 is conventionalenum 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