cplusplus / CWG

Core Working Group
23 stars 7 forks source link

CWG2932 [dcl.enum] Value range of empty enumeration without fixed underlying type. #604

Open keinflue opened 2 weeks ago

keinflue commented 2 weeks ago

Full name of submitter (unless configured in github; will be published with the issue): Benjamin Sch.

Reference (section label): [dcl.enum]

Issue description:

enum E { };
constexpr auto x = static_cast<E>(-1);

[dcl.enum]/8 specifies the range of E to be that of an (hypothetical) integer type with minimal width able to represent all enumerator values. For an empty enumeration [dcl.enum]/8 also specifies that the rule is applied as if a single enumerator with value 0 was present.

The minimal width here is 1, but both a signed and unsigned integer type with width 1 are able to represent the value 0. If it is the unsigned one, then static_cast<E>(-1) is UB and the declaration of x ill-formed. If it is the signed one, then -1 can be represented and static_cast<E>(-1) is not UB and the declaration of x well-formed.

Which of the two determines the value range of E?

If it is intentionally unspecified, then the static_cast with unspecified UB should be explicitly disallowed as core constant expression.

t3nsor commented 2 weeks ago

The range is intended to be just {0}. This was clear before P1236. After P1236, the intent is that you think of it as an unsigned type with width 0, which of course is narrower than a signed type with width 1. But the wording doesn't make this super clear, because unsigned types with width 0 are impossible (they wouldn't have a signed counterpart). So I think we need some wording fix here.

jensmaurer commented 2 weeks ago

CWG2932

keinflue commented 2 weeks ago

@jensmaurer The issue mistakenly refers to Section 9.6 [dcl.struct.bind], when it should be 9.7.1 [dcl.enum].

jensmaurer commented 2 weeks ago

Fixed.

t3nsor commented 2 weeks ago

"The width of the smallest bit-field large enough to hold all the values of the enumeration type is M" - but if M is 0, this is incorrect because a bit-field of width 0 can't have any value at all.

keinflue commented 2 weeks ago

@t3nsor As far as I can tell (and correct me please) that sentence seems to be relevant to figure out whether a bit-field is large enough for the purpose of https://eel.is/c++draft/class#bit-4.sentence-3. Since a zero-width bit-field must be unnamed and thus can't be assigned a value, it doesn't matter whether M is 0 or 1 for that purpose.

t3nsor commented 2 weeks ago

"The width of the smallest bit-field large enough to hold all the values of the enumeration type is M."

This sentence doesn't just say that a bit-field must have width at least M to be able to hold all values of the enumeration. It says

The width of the smallest bit-field large enough to hold all values of the enumeration

is (i.e., is identical to)

M.

But the former quantity is 1, so if M is 0 then the statement is false.

dimitry-ishenko commented 1 week ago

Looking at the timing, this issue was possibly inspired by my SO question here: https://stackoverflow.com/q/78933393/4358570

While the proposed solution addresses the example with an empty enum, consider the following:

enum foo { bar, baz, qux };
constexpr auto barf = static_cast<foo>(-1);

Size M of the bit-field would be 2 in this case. But, what about the range of values? Should it be [0..3] (ie, unsigned int :2) or [-2..1] (ie, int :2)?

t3nsor commented 1 week ago

If it were signed, the range would be [-2, 1].

dimitry-ishenko commented 1 week ago

@t3nsor sorry, yes. fixed.

t3nsor commented 1 week ago

So what's the issue? It has to be the unsigned one, because the signed one wouldn't be able to hold the value 2.

dimitry-ishenko commented 1 week ago

@t3nsor oh, you are right. Unsigned enum will always be implicitly chosen, since it can hold more positive values. Disregard my example, then.

keinflue commented 1 week ago

@jensmaurer If that is ok, I'd like to request the issue's wording to be extended to reflect the ambiguity I originally saw:

It is unclear whether the hypothetical integer type for E is a signed integer type of width 1, an unsigned integer type of width 1 or an unsigned integer type of width 0, which does not have a signed counterpart and thus does not exist. The former choice makes the example well-formed, the latter oneones ill-formed. Before P1236, the specification was clear.

jensmaurer commented 1 week ago

@t3nsor , seems like the desired outcome is M=1 with an unsigned integer type for the empty enum example, right?

t3nsor commented 1 week ago

@t3nsor , seems like the desired outcome is M=1 with an unsigned integer type for the empty enum example, right?

I think the desired outcome is M = 0 (so the only possible value it can hold is 0), but the minimum bit-field width should be 1, because a bit-field of width 0 can't hold any value at all.