Is enum E { a } e = E(2); valid?

Viewed 108

A recent and interesting C language-lawyer question asked:

This Q&A asks the same question for the similar cases for C++(*): are these valid?

enum EA { a } ea = EA(2);               // #1
enum EB { b } eb = static_cast<EB>(2);  // #2

I've already noted that the variation enum EC { c } ec = EC{2}; is rejected at compile time ("cannot initialize a value of type 'E' with an rvalue of type 'int'), whereas #1 and #2 are accepted, ruling out ill-formed but not ill-formed NDR and UB for #1 and #2.


(*) Posted here as a self-answered Q&A after being prompted to break out overly eager and misplaced C++-focused answer to the original C language-lawyer question.

1 Answers

Unspecified behavior until C++14; undefined behavior since C++17

Whilst the corresponding construct is legal in C—as answered thoroughly in the linked-to C language-lawyer Q&A—due to the stronger "compatible type" requirement, the same does not hold for C++, where "C style" unscoped enums with no fixed underlying type have subtle differences from C.

First of all, we may note that the original C example

enum E { a } e = 1;

is not legal in C++ as C++ does not allow implicit conversion from an integer type to an unscoped enum with no explicit underlying type, thus the slightly modifyed examples of the OP

// Syntactically legal -> compiles.
enum EA { a } ea = EA(2);               // #1
enum EB { b } eb = static_cast<EB>(2);  // #2

As per [dcl.enum]/7,

For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, the values of the enumeration are the values representable by a hypothetical integer type with minimal width M such that all enumerators can be represented. The width of the smallest bit-field large enough to hold all the values of the enumeration type is M. [...]

the underlying type of EA and EB is implementation-defined.

As per [expr.static.cast]/10:

A value of integral or enumeration type can be explicitly converted to a complete enumeration type. [...] If the enumeration type does not have a fixed underlying type, the value is unchanged if the original value is within the range of the enumeration values ([dcl.enum]), and otherwise, the behavior is undefined. [...]

#2 above may (depending on the implementation-defined underlying type of the E) lead to

  • undefined behavior in C++17 and forward, via the resolution of CWG 1766, and
  • unspecified behavior in C++14 and earlier.

Conceptually, the conversion could possibly cause signed integer overflow (which is UB).

Case #1 is, as per [expr.type.conv], also an explicit (type) conversion, and the same argument as for #2 applies also for this functional cast case.

Related