Authors: Jay Ghiron
Date: 2026-11-04
Submitted against: C23
Status: Open
Cross-references: 1021
This issue comes from GCC issue 128335. Consider the following declarations:
enum E{A=1,B=_Generic(A,int:-1U,default:1U)};
enum E{A=1,B=_Generic(A,int:-1U,default:1U)};
During the processing of each enumeration constant in the enumerator list, the type of the enumeration constant shall be:
the previously declared type, if it is a redeclaration of the same enumeration constant; or,
the enumerated type, for an enumeration with fixed underlying type; or,
int, if there are no previous enumeration constants in the enumerator list and no explicit=with a defining integer constant expression; or,
int, if given explicitly with=and the value of the integer constant expression is representable by anint; or,the type of the integer constant expression, if given explicitly with
=and if the value of the integer constant expression is not representable byint; or,the type of the value from the previous enumeration constant with one added to it. If such an integer constant expression would overflow or wraparound the value of the previous enumeration constant from the addition of one, the type takes on either:
- a suitably sized signed integer type, excluding the bit-precise signed integer types, capable of representing the value of the previous enumeration constant plus one; or,
- a suitably sized unsigned integer type, excluding the bit-precise unsigned integer types, capable of representing the value of the previous enumeration constant plus one.
A signed integer type is chosen if the previous enumeration constant being added is of signed integer type. An unsigned integer type is chosen if the previous enumeration constant is of unsigned integer type. If there is no suitably sized integer type described previously which can represent the new value, then the enumeration has no type which can represent all its values.
(C23 6.7.3.3 "Enumeration specifiers" paragraph 12.)
In the first generic selection the type of A is clear, it has the
type int which is clearly defined by the fourth bullet in the quoted
paragraph. The second generic selection relies on the first bullet in
the quoted paragraph, which says the type is the "previously declared
type". As far as I understand this should mean the same type is used
as was given to the first declaration of A by the quoted paragraph,
so the declarations are valid. However, it does not seem unreasonable
to interpret it as referring to the underlying type or the enumerated
type itself. GCC currently uses the underlying type which causes the
declarations to be invalid, however Martin Uecker has indicated that
the type should be the enumerated type instead. It would be useful to
clarify which interpretation is correct. Clang errors on the original
example too for unrelated reasons, see also LLVM issue
237578.
As an aside, it also never seems to be stated that the bullets in this
paragraph are interpreted in order. It therefore appears that an
implementation could have multiple types to choose from, for example
in enum F:int{C} the type of C could either be enum F or int
before the closing brace.
Modify C23 6.7.3.3 paragraph 12:
During the processing of each enumeration constant in the enumerator list, the type of the enumeration constant shall be:
the previously declared type as specified in this paragraph, if it is a redeclaration of the same enumeration constant; otherwise,
the enumerated type, for an enumeration with fixed underlying type; otherwise,
int, if there are no previous enumeration constants in the enumerator list and no explicit=with a defining integer constant expression; otherwise,
int, if given explicitly with=and the value of the integer constant expression is representable by anint; otherwise,the type of the integer constant expression, if given explicitly with
=and if the value of the integer constant expression is not representable byint; otherwise,the type of the value from the previous enumeration constant with one added to it. If such an integer constant expression would overflow or wraparound the value of the previous enumeration constant from the addition of one, the type takes on either:
- a suitably sized signed integer type, excluding the bit-precise signed integer types, capable of representing the value of the previous enumeration constant plus one; or,
- a suitably sized unsigned integer type, excluding the bit-precise unsigned integer types, capable of representing the value of the previous enumeration constant plus one.
A signed integer type is chosen if the previous enumeration constant being added is of signed integer type. An unsigned integer type is chosen if the previous enumeration constant is of unsigned integer type. If there is no suitably sized integer type described previously which can represent the new value, then the enumeration has no type which can represent all its values.
Insert a new paragraph at the end of C23 6.7.3.3:
EXAMPLE 7 The following fragment:
enum G { A, B, C, D, F, E = -1U + 0UL }; static_assert(_Generic(+A, enum G: 1, default: 0)); /* assumption */ enum H : typeof(+A); /* incompatible with enum G, however enum H is compatible with the underlying type of enum G */ static_assert(_Generic(A, enum G: _Generic(A, enum H: 0, default: 1), default: 0 )); /* each enumeration constant of enum G has the type enum G outside of any definition of enum G */ enum J : int { K }; enum L : int { M }; /* enum J and enum L are incompatible, but are both compatible with int */ enum N : unsigned long { O }; enum P : unsigned long { Q }; /* enum N and enum P are incompatible, but are both compatible with unsigned long */ enum G { A = _Generic(B, enum G: _Generic(B, enum H: -1, default: 0), default: -1 ), /* each B occurs before the declaration of B in this definition of enum G, the type is still enum G */ B = _Generic(B, enum G: _Generic(B, enum H: -1, default: 1), default: -1 ), /* same as A, the declaration of B in this definition of enum G is not complete in the generic selection */ C = _Generic(B, enum J: _Generic(B, enum L: 2, default: -1), default: -1 ), /* each B now has type int, not enum G */ D = _Generic(E, enum G: _Generic(E, enum H: -1, default: 3), default: -1 ), /* each E occurs before the declaration of E in this definition of enum G, the type is still enum G */ E = _Generic(E, enum G: _Generic(E, enum H: -2U, default: -1U), default: -2U ), /* same as D, the declaration of E in this definition of enum G is not complete in the generic selection */ F = _Generic(E, enum N: _Generic(E, enum P: 4, default: -1), default: -1 ) /* each E now has type unsigned long, not enum G or unsigned int */ }; /* different order of enumerators is allowed */demonstrates the types of enumeration constants assuming the first
static_assertdeclaration succeeds.
Note: This example relies on issue 1021 being resolved.
Note: This example is deliberately designed to require exact types of the enumeration constants and to catch implementation bugs.