C++17:direct-list-initialization of enums

In this article, we are going to learn about the C++ 17 feature of List-initialization of scoped enums. This feature allows programmers initialize enums with distinct type-safe integer types.

Strongly types enums or scoped enums were introduces in C++ 11 , but there were some vulnerabilities or you can say loop holes that were faced by the developers. So to overcome those problems, in C++ 17 we have something new for strongly typed enums and that
is a new methods to initialize the enums.

Let us first see the problems before we jump to the improvements to solve those problems.

Until C++17, there was no clean way to initialize a scoped enum with an underlying integer value. Now let us see with example what does that mean.

Scoped enums do not implicitly convert to and from integer values. This strong type-safety of scoped enums restricts developers from using them like the old style enums, which used to convert implicitly to and from integer values.

so if you declare a scoped enum and you want to assign it an integer value it will look like this from a developer’s code.

enum class Number : uint16_t { }; 
Number bigNumber = 42425;

But unfortunately this will not compile, because an implicit conversion to an integer of number 42425 to bigNumber will fail. Developers were confused with this and frustrated as well.

What adds to more confusion is code like this.

enum class Number : uint16_t { }; 
Number bigNumber = Number(42425);

In this code , developer just tried to do the casting of the number 42425 to enum Number. It works. 🙂
But wait, there is problem with this also at runtime, because this casting may cost you a lot if this value gets truncated in this casting. So off course it compiles , but at runtime results may not be as per your expectations, if someone try it like this, passing a number outside the range.

Number bigNumber = Number(424255555555555);

So as a solution to the headaches developers were facing with strongly typed enums, C++17 came up with the idea of braced-initialization (“{}”) of enums by their underlying value. It solved the first problem of direct initialization. Now a developer can do :

Number bigNumber{42425};
instead of doing wrong and getting error with code like this:
Number bigNumber = 42425;

Second problem is also solved with the idea of braced-initialization (“{}”). Braced-initialization is safer than functional cast because it avoids the narrowing conversion and it flags you a error at the compile time itself. This avoids the runtime failure and data losses. so if you try this line now:

Number bigNumber = Number(424255555555555);

This will fail at compile time itself and flag you that this is beyond the conversion range.

Rules for strongly types enums list initialization

Both strongly types enums or scoped enumeration types and native enum types whose data type is declared can be initialized from an integer without a cast, using list initialization, if all of the following is true:

  1. The initialization is direct-list-initialization by using the braced-initialization (“{}”).
  2. The initializer list has only a single element to assign value.
  3. The enumeration is either scoped or unscoped with underlying type fixed the conversion is non-narrowing.

Example of strongly typed enums list initialization.

// The data type is fixed as unsigned char.
enum byte : unsigned char {};

// Initialization by using a single element. so works good.
byte byenum {0}; 
// Initiaization is with single element, but out of range value(0-255) for unsigned char. No conversion is possible so error.
byte byenum {-1}; 
// Initialization by using a single element. so works good.
byte byenum = byte{1}; 
// Initiaization is with single element, but out of range value (0-255) for unsigned char. No conversion is possible so error.
byte byenum = byte{256}; 
// Initialization by using more than one element. so Error.
byte byenum = byte{1,2,3};