Safeguard Your enums: Make Them Strongly-Typed

num types have been a valuable asset in the arsenal of C++ programmers for many years. However, their current specification has some security loopholes that can lead to bugs and portability problems. Recently, the C++ standards committee accepted a new proposal for strongly typed enum types. As with all other C++09 proposals, it may take some time before your compiler supports this feature, but it’s not a moment too soon to learn how to use this feature or how to make your code C++09-ready.


How can you avoid implicit conversions of enumerators to integral types? Or specify the exact underlying type for a certain enum?


Use C++09 strongly-typed enums.

Implicit Conversions
Contemporary enum types (henceforth, I will refer to them as traditional enum types) are only partially type-safe. Indeed, it’s impossible to initialize an enum variable with an enumerator of a different enum type:

enum Set {E1=1, E2};enum Dir{Up, Down};Set s=E1; //OK, same types=2;//error, can't convert int to Sets=Down; //error, can't convert Dir to Set

However, implicit conversions of enum types to integral types are allowed. They can cause unpredictable runtime behavior, as the following example shows:

bool b=s; //dangerous, s's value is unknownb=E2; //undefined behavior

Some compilers will issue warning messages when they encounter such mishaps. However, it isn’t always possible to tell at compile time whether the value of enum variable can fit into a Boolean value. If s equals E1, the initialization of b is valid. If however s equals E2, b will have an indeterminate value.

Size Issues
The inability to specify a predictable and portable size for enum types is another problem, particularly in applications that serialize data or read data from remote databases. Consider the enumeration Set. A C++ compiler is free to store it in a char, or use a larger underlying type for it such as short, int, and long. This means that the sizeof (s) can range between one to four bytes, depending on the specific compiler and compilation flags used. Presently, standard C++ has no mechanism for specifying an underlying type of enum types. If you must have a specific underlying type, the current workaround consists of replacing enum types with plain constants:

const int32 E1=1;const int32 E2=2;

However, this approach is inelegant and dangerous. The programmer must provide an explicit value for each constant, risking the creation of duplicates:

//..long list of constants const int32 E73=1; //oops, same as E1

Traditional enums have one more problem. The scope of their enumerators propagates to their enclosing scope, often causing name conflicts and ambiguity:

enum Dir {Up, Down}; //Up & Down are globally visibleenum Rate {Up, Down};//error, Up & Down already definedint main(){ int Rate=5;//error, Height already defined}

Although Dir and Rate are distinct types, their enumerators are globally visible, causing name conflicts. In contemporary C++, there are two common workarounds to this problem:

  • declaring the enumerators in different namespaces
  • changing the enumerators’ names

The first technique is cumbersome and can still cause name conflicts if a source file has multiple using declarations. The latter is slightly better, but it isn’t always an option for programmers using third-party libraries and compiled binaries. It’s time to see how the new strongly-typed enums solve all these problems elegantly.

enum Classes
Appending the class keyword to an enum declaration creates a strongly typed enum. Unlike a traditional enum, a strongly-typed enum isn’t implicitly converted to integral types:

enum class Set //C++09 strongly-typed enum{  E1,  E2,  E3 = 4,  E4 = 8};  void g(Set s ) { if( s > 10 ) // error, can't convert Set to int Set s2=Set::E1; //OK}

Strongly-typed enums are represented as int by default. However, you can specify a different underlying type using inheritance-like syntax. For example, to specify an underlying type of unsigned short for Set, declare it like this:

enum class Set: unsigned short {  E1,  E2,  E3 = 4,  E4 = 8 };  

Scope
Strongly-typed enumerators’ scope is restricted to their enclosing enumeration. It doesn’t propagate to the enclosing scope. This property reduces the risk of name conflicts and ambiguities to a minimum. To refer to such enumerators from an external scope, use their qualified names:

enum class Set: char {E1, E2};  Set s=E1; //error, E1 isn't recognized in this scopeSet s=Set::E1; //OK, qualified enumerator used

Ease and Safety
Strongly typed enums can coexist with traditional enums. Therefore, legacy code will continue to work as expected even after this feature is added to C++. However, upgrading traditional enums to strongly-typed ones is an easy and welcome step. Simply add the class keyword to the declarations of traditional enum types and recompile. To make your existing enums C++09-ready and facilitate future migration, you can use conditional compilation for the time being:

#ifdef CPP09_STRONGLY_TYPED_ENUMS#define CPP09_ENUM class#else#define CPP09_ENUM #endifenum CPP09_ENUM Color //traditional or strongly-typed{ Red,  Green,  Blue};  
Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Related Posts