I wonder if the following should or should not compile in C++17
enum class E
{
A, B
};
constexpr E x = static_cast<E>(2);
This compiles with both GCC 9.3.0 and Clang 10.0.0 on Ubuntu 20.04.
My question is
- Should this compile?
- If it should, why?
I don’t think it should. There are many posts regarding undefined behaviour (UB) and enums, but I couldn’t find any that brought it up in a constant expression context. Also, most posts use an underlying type, I consider scoped enums without any fixed underlying type. Since I don’t have access to a copy of an ISO standard, I will refer to the latest C++17 draft found a cppreference.com here in the reasoning below.
First, we find the paragraph that discards expressions with UB from being constant expressions
[expr.const]/2: An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
with subsection
[expr.const]/2.6: an operation that would have undefined behavior as specified in Clauses [intro] through [cpp] of this International Standard [ Note: including, for example, signed integer overflow (Clause [expr]), certain pointer arithmetic ([expr.add]), division by zero, or certain shift operations — end note ] ;
which tells us that constant expressions may not contain UB.
Then, we find the section regarding static casts from int to enum
[expr.static.cast]/10: A value of integral or enumeration type can be explicitly converted to a complete enumeration type. The value is unchanged if the original value is within the range of the enumeration values ([dcl.enum]). Otherwise, the behavior is undefined. A value of floating-point type can also be explicitly converted to an enumeration type. The resulting value is the same as converting the original value to the underlying type of the enumeration ([conv.fpint]), and subsequently to the enumeration type.
which tells us that the result of a static cast is well defined if the operand is within the range of the target enum. The section refers to [decl.enum]/8 regarding the range of the enumeration values (too long to post here, I can't get the formatting right either). Anyways, it says that the range of valid values of an enum of non-fixed underlying type is defined by the smallest bitset that can fit all values between the smallest and the biggest enumeration (in two-complements format).
Finally, applying these three sections on the example code, we can say that the smallest bitfield that can contain both A = 0 and B = 1 is of size one. Hence, the integer 2 (which requires two bits to represent in two-complements format) is outside the range of the target enum E, and the static cast has undefined behaviour. Since a constexpr variable can’t have undefined behaviour, the program should not compile.
The closest I could get to the answer is in this post where they discuss whether the compiler is required to issue a diagnostic in the case of UB in constexpr: TLDR of that post is: Yes, but a warning might suffice to be complaint with the standard.