37

First, I want to say, according to cppreference.com, it is somewhat impossible to value-initialize an enum.

According to http://en.cppreference.com/w/cpp/language/value_initialization, value-initializing an enum actually performs zero-initialization. It then follows that, according to http://en.cppreference.com/w/cpp/language/zero_initialization, the effect of zero-initializing an enum is:

If T is a scalar type, the object's initial value is the integral constant zero implicitly converted to T.

However, an integral constant zero is not implicitly convertible to an enum. Ultimately, an enum cannot be value-initialized. This sounds weird, and value-initializing an enum does work on VC, GCC, and clang. So, what does the standard say about this?

Second, according to http://en.cppreference.com/w/cpp/language/static_cast:

Integer, floating-point, or enumeration type can be converted to any complete enumeration type (the result is unspecified (until C++17) undefined behavior (since C++17) if the value of expression, converted to the enumeration's underlying type, is not one of the target enumeration values)

So, does this imply that value-initializing an enum (if it works at all) may actually lead to undefined behavior if the target enum does not have an enumerator equal to 0?

Mark B
  • 95,107
  • 10
  • 109
  • 188
Lingxi
  • 14,579
  • 2
  • 37
  • 93
  • 4
    Re: second. That's not what the standard says. From C++14: "**5.2.9/10** A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is *within the range of the enumeration values (7.2)*. Otherwise, the resulting value is unspecified (and might not be in that range)." Emphasis mine. The range of enumeration values is defined (by **7.2/8**) in such a way that 0 always falls into it. – Igor Tandetnik Jan 14 '15 at 04:59
  • @IgorTandetnik cppreference updated – Cubbi Jan 14 '15 at 11:32
  • 2
    The description of zero-initialization is also wrong. [dcl.init]/6.1 says that "if `T` is a scalar type (3.9), the object is initialized to the value obtained by converting the integer literal `0` (zero) to `T`". Note the lack of "implicit". @Cubbi The fix for `static_cast` is still off; the current description only applies to enumerations whose underlying type is not fixed. Per [dcl.enum]/8, "For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type." – T.C. Feb 03 '15 at 08:13
  • @T.C. edited, thanks! Don't hesitate to correct anything yourself, it's publically-editable. – Cubbi Feb 03 '15 at 11:17

2 Answers2

4

1: This can be undestood like so:

enum class SomeEnum : int { V1 = 0, V2 = 1, V3 = 2 }; 
SomeEnum a = 0; // compile error
SomeEnum b = SomeEnum.V1; // OK

This is basic protection from undefined behavior!

2: Yes and Yes :)

SomeEnum c = static_cast<SomeEnum>(1); // = SomeEnum.V2
SomeEnum d = static_cast<SomeEnum>(5); // undefined behavior

static_cast is dangerous by definition, it shoud only be used to support serrialization or old c interfaces!

Mux
  • 348
  • 1
  • 6
  • 1
    Funnily, this compiles without error and "works fine" in total defiance of being correct: http://coliru.stacked-crooked.com/a/21a0f0256e412848 (tested with both GCC and Clang). – Damon Mar 05 '15 at 16:58
  • 3
    `static_cast(5);` is not UB. `5` is in the value representable by the enum; you even explicitly state that the underlying type is `int`. See: [What happens if you static_cast invalid value to enum class?](http://stackoverflow.com/q/18195312) – dyp Mar 05 '15 at 17:30
  • technically `static_cast(5);` is valid, but from point of view it is not, bc there is not respective value. so from the language point of view the behavior is UD, yet predictable in practice – Mux Mar 05 '15 at 19:14
  • 3
    No, the language precisely specifies the behaviour of `static_cast(5)`. You, or any programmer for that matter, may not expect a variable of enumeration type to compare unequal to all enumerators of that enumeration, but the language does not make any such guarantee. One way to use enumeration values without corresponding enumerator is to convert to the underlying type, i.e. `static_cast(static_cast(x)) == x` is true for all values `x` that can be represented as an `int` (and the language guarantees that), given that `int` is the underlying type of `SomeEnum`. – dyp Mar 05 '15 at 21:30
4

The answer to this was given in the comments. My attempt of explaining the entire standardese behind it is given below.

To zero-initialize an object or reference of type T means:

  • if T is a scalar type (3.9), the object is initialized to the value obtained by converting the integer literal 0 (zero) to T;

(Enumerations are scalar types; §3.9/9) So as the conversion is not said to be implicit, we're not looking in §4, but §5.2.9;

The result of the expression static_cast<T>(v) is the result of converting the expression v to type T.

§5.2.9/10 then defines how integral values are converted to enumeration types.

A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2). Otherwise, the resulting value is unspecified (and might not be in that range).

It must be shown that zero is in the range of enumeration values for all enumerations.
The next five quotes are taken from §7.2/8:

For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type.

Since all permitted underlying types include zero in their range of values*, this automatically gives the desired result. Now, for enumerations without fixed underlying types,

Otherwise, for an enumeration where emin is the smallest enumerator and e max is the largest, the values of the enumeration are the values in the range b min to b max , defined as follows:

I.e. we have to show that bmin is always less than or equal to zero, and bmax is always greater or equal to zero.

Let K be 1 for a two’s complement representation and 0 for a one’s complement or sign-magnitude representation.
b max is the smallest value greater than or equal to max(|e min| − K, |e max|) and equal to 2M − 1, where M is a non-negative integer.

|e max| is non-negative, and the maximum of two numbers is at least as large as both numbers are. Hence max(|e min| − K, |e max|) is non-negative as well, and bmax must be greater or equal to that number - so our first requirement is met.

b min is zero if emin is non-negative and −(bmax + K) otherwise.

bmin is clearly either zero or negative: bmax is non-negative as shown above, and K is non-negative (0 or 1), hence the additive inverse of their sum is non-positive. Our second requirement is met. Finally,

If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0.

This leads to the above result by setting emin = emax = 0.


  • This reduces to the claim "All integral types have zero in their range of values", which is left to prove for the reader.
Columbo
  • 60,038
  • 8
  • 155
  • 203