https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126353

            Bug ID: 126353
           Summary: Out-of-range static_cast to unscoped enum without
                    fixed underlying type not rejected in constant
                    expression
           Product: gcc
           Version: 16.1.1
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: gongke at ios dot ac.cn
  Target Milestone: ---

Consider the following code:

```cpp
#include <iostream>

// underlying type not-fixed --> M = 2 --> values of the enumeration type are
0, 1, 2, and 3
enum E { a = 1, b = 2 };

constexpr int test() {
    E e = static_cast<E>(4); // UB: 4 is outside the range of E
    return static_cast<int>(e);
}

int main() {
    constexpr int x = test(); // forces compile-time evaluation
    std::cout << x << '\n';
    return 0;
}
```

This code with `-std=c++20`, is rejected by Clang but accepted by GCC.
[godbolt](https://godbolt.org/z/35Kbo73cd)

Taking the draft N4861 as C++20's reference. [expr.static.cast]/10 says that
> A value of integral or enumeration type can be explicitly converted to a 
> complete enumeration type. If the enumeration type has a fixed underlying 
> type, the value is first converted to that type by integral conversion, if 
> necessary, and then to the 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 (9.7.1), and otherwise, the 
> behavior is undefined.

[dcl.enum]/5:
> Each enumeration defines a type that is different from all other types. Each 
> enumeration also has an underlying type. The underlying type can be 
> explicitly specified using an enum-base. For a scoped enumeration type, the 
> underlying type is int if it is not explicitly specified. In both of these 
> cases, the underlying type is said to be fixed.

This means that this `E` is an enumeration type without a fixed underlying
type.

[dcl.enum]/8:
> 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 . It is possible to define an enumeration that has 
> values not defined by any of its enumerators. If the enumerator-list is 
> empty, the values of the enumeration are as if the enumeration had a single 
> enumerator with value 0.

So the enumeration values of `E` are {0, 1, 2, 3} and here M=2. Hence this
static_cast is undefined behavior and should be rejected when
constant-evaluated. Clang correctly rejects this and states this reason in the
diagnostic message.

Reply via email to