2

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

  1. Should this compile?
  2. 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.

cigien
  • 57,834
  • 11
  • 73
  • 112
skalet
  • 365
  • 1
  • 9

1 Answers1

8

Scoped enums always have fixed underlying type. [dcl.enum]/5 (C++17):

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.

So your E has fixed underlying type of int. Then in paragraph 8:

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

2 is in range for int, so by the text you quoted from [expr.static.cast], the behaviour of the cast is well-defined.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Ofc! Missed the clause that defines "fixed" (i was thinking explicitly specified). That's why one can forward declare "enum class E" without specifying the underlying type. Ok, so the obscure clause about bitfields should still apply for plain old enums, if you don't explicitly specify the underlying type? How would my reasoning apply in that case? – skalet Mar 17 '21 at 06:31
  • @M.M the code still compiles with unscoped enums without fixed size, what am I missing? https://godbolt.org/z/sn8xnaWa6 – user1011113 Jun 14 '21 at 12:28
  • As a matter of fact, even the UB example from cppreference compiles fine with constexpr, both for GCC and Clang: https://godbolt.org/z/WWG6vszMf – user1011113 Jun 14 '21 at 12:33
  • @user1011113 this question is about `enum class E {` , I would suggest posting a new question (if you can't find one that addresses your issue) – M.M Jun 14 '21 at 20:43
  • Thanks. As a matter of fact the OP actually posted the question already :) https://stackoverflow.com/questions/66719602/undefined-behavior-of-constexpr-static-cast-from-int-to-unscoped-enum-with-non-f – user1011113 Jun 15 '21 at 06:19