Issue 1130: Redeclarations of enumeration constants

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:

(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.

Suggested correction

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:

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_assert declaration 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.