45

As requested by Bathsheba and as a follow up question to "What happens if an enum cannot fit into an integral type?":

Asuming an enum is defined as follows :

enum foo : unsigned int
{
    bar = UINT_MAX,
    oops
};

Is the value of oops defined or isn't it?


MSVS2015 compilation:

warning C4340: 'oops': value wrapped from positive to negative value
warning C4309: 'initializing': truncation of constant value
warning C4369: 'oops':  enumerator value '4294967296' cannot be represented as 'unsigned int', value is '0'

MSVS2015 output:

bar = 4294967295
oops= 0

gcc 4.9.2 compilation:

9 : note: in expansion of macro 'UINT_MAX'
bar = UINT_MAX,
^
10 : error: enumerator value 4294967296l is outside the range of underlying type 'unsigned int'
oops
^
Compilation failed

gcc 4.9.2 output

//compilation failed
Community
  • 1
  • 1
Simon Kraemer
  • 5,700
  • 1
  • 19
  • 49
  • Upvoted as promised. I can't see anything in http://en.cppreference.com/w/cpp/language/enum – Bathsheba Sep 21 '16 at 15:46
  • 1
    Seems like `int` would be a more interesting question, because then the computation of `bar + 1` causes UB. – Kerrek SB Sep 21 '16 at 15:48
  • 1
    @KerrekSB: that's essentially the crux of my answer on the linked question. – Bathsheba Sep 21 '16 at 15:49
  • The value of `UINT_MAX + 1` is defined. `oops` is an unsigned int enum. Why wouldn't it be defined? – Shmuel H. Sep 21 '16 at 15:52
  • @Bathsheba I don't know, maybe he has a point. – Shmuel H. Sep 21 '16 at 15:53
  • 1
    @ShmuelH. I thought so but shouldn't it compile with gcc then? – Simon Kraemer Sep 21 '16 at 15:56
  • 1
    Compilation fails using GCC 6.1.0. too. – sjrowlinson Sep 21 '16 at 15:58
  • 4
    Hum. UINT_MAX + 1 is obviously a compile time evaluable constant expression, but given that it can't fit into an `unsigned`, the C++ standard wants the compiler to pick a long long as the enum type, but can't since you've forced it to be an `unsigned`. To me that explains the compiler errors and could even be the answer. The line in the standard "If the underlying type is fixed, the values can be converted to their promoted underlying type" also has something to do with it methinks. – Bathsheba Sep 21 '16 at 15:58
  • @Bathsheba Yes, but if it would compile with some compiler, it should be defined. – Shmuel H. Sep 21 '16 at 16:04
  • 4
    @Bathsheba Just tested to see and if you do not limit the type you just get a warning: http://coliru.stacked-crooked.com/a/a8306b64279b700c – NathanOliver Sep 21 '16 at 16:06
  • 1
    @NathanOliver With gcc (6.2.1), you don't even get a warning. – Shmuel H. Sep 21 '16 at 16:08
  • 2
    [dcl.enum]/2 and 7 seem to be relevant here. _"if the initializing value of an enumerator cannot be represented by the underlying type, the program is ill-formed."_ is interesting, but I am not really sure if it applies. Currently trying to fugure standartese there. – Revolver_Ocelot Sep 21 '16 at 16:10
  • Visual Studio 2013 here: there is a warning for `unsigned`, but for `enum foo : unsigned long long` with `bar = ULLONG_MAX`, there is no warning, even though the value overflow behavior stays the same. – grek40 Sep 21 '16 at 17:24

1 Answers1

20

This is a very interesting question. The simple answer is that this is literally undefined: the standard doesn't say anything about this case.

To have a better example, consider this enum:

 enum foo : bool { True=true, undefined };

According to the standard:

[dcl.enum]/2: [...] An enumerator-definition without an initializer gives the enumerator the value obtained by increasing the value of the previous enumerator by one.

Therefore, the value of foo::undefined in our example is 2 (true+1). Which can not be represented as a bool.

Is it ill-formed?

No, according to the standard, it is perfectly valid, only not-fixed underlying type have a restriction about not being able to represent all of the enumerator values:

[dcl.enum]/7: For an enumeration whose underlying type is not fixed, [...] If no integral type can represent all the enumerator values, the enumeration is ill-formed.

It says nothing about a fixed underlying type that can not represent all the enumerator values.

What is the value of original question's oops and undefined?

It is undefined: the standard doesn't say anything about this case.

Possible values for foo::undefined:

  • Highest possible value (true): undefined and oops should be underlying type's maximum value.
  • Lowest possible value (false): the underlying type's minimum value. Note: In signed integers, it would not match the current behavior for Integer overflow (undefined behavior).
  • Random value (?): the compiler will choose an value.

The problem with all of these values is that it may result two fields with the same value (e.g. foo::True == foo::undefined).

The difference between initializer (e.g. undefined=2) and "implicit" initializer (e.g. True=true, undefined)

According to the standard:

[dcl.enum]/5: If the underlying type is fixed, the type of each enumerator prior to the closing brace is the underlying type and the constant-expression in the enumerator-definition shall be a converted constant expression of the underlying type.

In other words:

 enum bar : bool { undefined=2 };

is equivalent to

 enum bar : bool { undefined=static_cast<bool>(2) };

And then bar::undefined will be true. In an "implicit" initializer, it would not be the case: this standard paragraph say it about only initializer, and not about "implicit" initializer.

Summary

  1. With this way, it is possible for an enum with a fixed-underlying-type to have unrepresentable values.
  2. Their value is undefined by the standard.

According to the question and comments, this is not valid in GCC and clang but valid for MSVS-2015 (with a warning).

Shmuel H.
  • 2,348
  • 1
  • 16
  • 29
  • 1
    I like your answer but is it truely undefined if the underlying type is unsigned? You have quoted `[dcl.enum]/7`yourself which should (as overflow is designed for unsigned types) lead to the value that exceeds the range first to become 0 again... – Simon Kraemer Sep 22 '16 at 07:43
  • 1
    Also I am not sure that `bool` is the best choice here as the underlying data is neither signed nor unsigned and it doesn't overflow either (AFAIK), It's a pretty special case. – Simon Kraemer Sep 22 '16 at 07:48
  • 1
    @SimonKraemer I thought like you, but you should keep in mind that an enumerator value's type is not the same as its underlying-type. In other words, the standard should have said that an enumerator value should be converted into its *underlying-type (see the `The difference between initializer (e.g. undefined=2) and "implicit" initializer (e.g. True=true, undefined)` section). – Shmuel H. Sep 22 '16 at 08:00
  • Thanks for clarifying. – Simon Kraemer Sep 22 '16 at 08:03
  • 1
    @SimonKraemer And just a point for fun: It would clear as day that in a case of a `not-fixed underlying type enum`, it would be ill-formed - with a regular `unsigned int`: the standard says it explicitly in [dcl.enum]/7. – Shmuel H. Sep 22 '16 at 08:05