1. Changelog
1.1. Revision 3 - July 19th, 2022
- 
     All insertions and,deletions are highlighted specially so they can be observed, as well as being listed below this bullet in this part of the changelog.
- 
     Minor/editorial wording changes: - 
       Removed implementation-defined from Paragraph 5. (Paragraph 6 already explains that the enumerated type is compatible with some implementation-defined integer type.) 
- 
       Typo fix: enumeration member types enumeration member type 
- 
       Editorial fixes to FN✨1). 
 
- 
       
- 
     Wrote down additional motivation for why we have the long explanation of type/math computation for the values in § 4.1 Type-preserving, Value-Preserving. 
1.2. Revision 2 - June 17th, 2022
- 
     Address the use of both overflow and wraparound for signed and unsigned types in the "add 1 
- 
     Separate out two terms: - 
       enumerated type for the type of the enumeration itself; and, 
- 
       enumeration member type for the type of the enumeration members. (Thanks, Joseph Myers!) 
 
- 
       
- 
     Various typo and grammar fixes. 
- 
     Added a new section describing whether or not this is a breaking change insofar for the compilers we surveyed at § 4.3 Mis-Compiles. 
1.3. Revision 1 - April 12th, 2022
- 
     More directly specify the algorithm for selecting the types of enumeration constants, both after and midway through the definition of an enumeration. 
- 
     Move all of the specification for the new algorithm into §6.7.2.2 in the § 5.2.2 Modify Section §6.7.2.2 Enumeration constants. 
- 
     Add more rationale in § 4.2 Using the Enumerators Midway in the Definition List for the problems found in current implementation extensions. 
1.4. Revision 0 - January 1st, 2022
- 
     Initial release 🎉! 
2. Introduction and Motivation
C always designates 
This proposal provides for enumerations with enumerators of values greater than 
3. Prior Art
The design of this feature is to enable what has been existing practice on implementations for a long time now, including GCC, SDCC, Clang, and several other compilers. Compilers have allowed for values greater than 
4. Design
This is a very small change that only makes previously ill-formed code now well-formed. It does not provide any other guarantees from the new wording besides allowing constants larger than 
4.1. Type-preserving, Value-Preserving
More specifically:
- 
     values for enumerators that are outside of the range [ INT_MIN , INT_MAX ] int 
- 
     and, the underlying type for enumerations with such types may be larger than int 
As put more eloquently by Aaron Ballman, this paper aims to be value-preserving wherever possible for enumeration constants without breaking backwards compatibility. The sister paper aims to provide type-preserving properties, and both papers are meant to complement the existing ecosystem.
Particularly, this code:
enum a { a0 = 0xFFFFFFFFFFFFFFFFULL }; int main () { return _Generic ( a0 , unsigned long long : 0 , int : 1 , default : 2 ); } 
Should produce a value of 
4.2. Using the Enumerators Midway in the Definition List
Given this following code snippet:
#include <limits.h>#define GET_TYPE_INT(x) _Generic(x, \ char: 1,\ unsigned char: 2,\ signed char: 3,\ short: 4,\ unsigned short: 5,\ int: 6,\ unsigned int: 7,\ long: 8,\ unsigned long: 9,\ long long: 10,\ unsigned long long: 11,\ default: 0xFF\ )\ enum x { a = INT_MAX , b = ULLONG_MAX , a_type = GET_TYPE_INT ( a ), b_type = GET_TYPE_INT ( b ) }; #include <stdio.h>int main () { printf ( "sizeof(long)=%d \n " , ( int ) sizeof ( long )); printf ( "sizeof(long long)=%d \n " , ( int ) sizeof ( long long )); printf ( "a_type=%d \n " , ( int ) a_type ); printf ( "b_type=%d \n " , ( int ) b_type ); printf ( "GET_TYPE_INT(a), outside=%d \n " , ( int ) GET_TYPE_INT ( a )); printf ( "GET_TYPE_INT(b), outside=%d \n " , ( int ) GET_TYPE_INT ( b )); return 0 ; } 
Compilers are not consistent, depending on how far with extensions they like to go. Anyone who was depending on a specific type was not relying on either (a) compilable C code, according to the C standard, or (b) widely-existing cross-platform C code, according to what implementation extensions do. Therefore, we attempt to enshrine the best of the available existing practice and improve the status quo.
4.2.1. Midway Type: Whatever the Compiler Chooses
During the definition of an enumerated type, the enumeration constants themselves have whatever type the enumeration wants if they are not representable by 
4.2.2. Final Type: the Enumerated Type
After the enumerated type’s definition is completed (after the 
#include <limits.h>#define GET_TYPE_INT(x) _Generic(x, \ char: 1,\ unsigned char: 2,\ signed char: 3,\ short: 4,\ unsigned short: 5,\ int: 6,\ unsigned int: 7,\ long: 8,\ unsigned long: 9,\ long long: 10,\ unsigned long long: 11,\ default: 0xFF\ )\ enum x { a = INT_MAX , b = ULLONG_MAX , a_type = GET_TYPE_INT ( a ), b_type = GET_TYPE_INT ( b ) }; extern enum x e_a ; extern __typeof ( b ) e_a ; extern __typeof ( a ) e_a ; // … 
results in various failures on today’s implementations. This is because 
There is no way to make such code portable, as it was (A) already using an extension that was not standardized until before C23 and (B) already relied in an implementation detail that could change between implementations, but also within a given implementation (e.g., 
4.3. Mis-Compiles
Additionally, some compilers produce what could probably be considered a "mis-compile" of the code. For example, when given this code snippet:
enum a { a0 = 0xFFFFFFFFFFFFFFFFULL }; _Bool e () { return a0 ; } int f () { return a0 ; } unsigned long g () { return a0 ; } unsigned long long h () { return a0 ; } int main () { return f (); } 
One compiler will do the expected behavior of converting implicitly to each integer type and true/
Therefore, this proposal is not - insofar as understood by the author and the various folks asked to test for their compilers - breaking any code that was not already broken.
4.4. "Why not use the C++ Wording?" (Or: How We Ended Up With a Very Big ¶5 of Steps)
During the July 2022 Virtual C Meeting, it was repeatedly asked why we did not use the C++ wording, or just used the words "adds one to the previous enumerator" and left all the type computation of the intermediate values to the implementation. This was what the original wording in Revision 0 did. Unfortunately, this is not compatible with
- 
     (a) how math and operations are performed in C, wherein you need a type to determine the properties of a given operation; and, 
- 
     (b) what implementers were comfortable with when they read the wording. 
While I personally am glad C++ was able to achieve an unspoken consensus on how that worked for their compilers and everyone has more or less solved that problem in their compilers in a portable fashion with untyped enumerations, that is not where C is between its implementations. Given C enjoys a far wider number of implementations and many (several dozens) more implementations than C++, in some cases we need to be more strict and more explicit. This is especially true for cases like the following:
#include <limits.h>enum big_enum { a = LONG_MAX , b = a + 1 , c = ULLONG_MAX } 
The implementation needs to know, definitively, how to handle what 
5. Proposed Wording
The following wording is relative to N2912.
5.1. Intent
The intent of the wording is to provide the ability to express enumerations with the underlying type present. In particular:
- 
     enumerations without an underlying type can have enumerators initialized with integer constant expressions whose type is int 
- 
     bit-precise types cannot be used for enumerations without an underlying type; 
- 
     the type of the enumeration constants during definition may differ from when after the enumeration is completed; 
- 
     it is an error (constraint violation) to make an enumeration with a range of values that cannot be represented as as a single signed or unsigned type; and, 
- 
     it is an error (constraint violation) to make an enumeration with a value 
5.2. Proposed Specification
5.2.1. Modify Section §6.4.4.3 Enumeration constants
6.4.4.3 Enumeration constantsSyntaxenumeration-constant:
identifier
SemanticsAn identifier declared as an enumeration constant for an enumeration has either type
or the enumerated type, as defined in 6.7.2.2.int Forward references: enumeration specifiers (6.7.2.2).
5.2.2. Modify Section §6.7.2.2 Enumeration constants
6.7.2.2 Enumeration specifiersSyntaxenum-specifier:
enum attribute-specifier-sequenceopt identifieropt { enumerator-list }
enum attribute-specifier-sequenceopt identifieropt { enumerator-list , }
enum identifier
enumerator-list:
enumerator
enumerator-list , enumerator
enumerator:
enumeration-constant attribute-specifier-sequenceopt
enumeration-constant attribute-specifier-sequenceopt = constant-expression
ConstraintsThe expression that defines the value of an enumeration constant shall be an integer constant expression.
that has a value representable as anFor all the integer constant expressions which make up the values of the enumeration constant, there shall be.int an implementation-defined a signed or unsigned integer type (excluding the bit-precise integer types) capable of representing all of the values.SemanticsThe identifiers in an enumerator list are declared as constants and may appear wherever such are permitted.133) An enumerator with = defines its enumeration constant as the value of the constant expression. If the first enumerator has no =, the value of its enumeration constant is 0. Each subsequent enumerator with no = defines its enumeration constant as the value of the constant expression obtained by adding 1 to the value of the previous enumeration constant. (The use of enumerators with = may produce enumeration constants with values that duplicate other values in the same enumeration.) The enumerators of an enumeration are also known as its members.
The type for the members of an enumeration is called the
enumeration member types enumeration member type .During the processing of each enumeration constant in the enumerator list, the type of the enumeration constant shall be:
—
, 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,int 
— the type of the integer constant expression, if given explicitly with = and if the value of the integer constant expression is not representable by
; or,int 
— the type of the value from
last the previous enumeration constant withadded to it. If such an integer constant expression would overflow or wraparound the value of the previous enumeration constant from the addition of1 , the type takes on either:1 
— a suitably sized signed integer type (excluding the bit-precise signed integer types) capable of representing the value of the previous enumeration constant plus
; or,1 
— a suitably sized unsigned integer type (excluding the bit-precise unsigned integer types) capable of representing the value of the previous enumeration constant plus
.1 
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 previous which can represent the new value, then the enumeration has no type which is capable of representing all of its valuesFN0✨).
Each enumerated type shall be compatible with
, a signed integer type, or an unsigned integer type (excluding the bit-precise integer types) . The choice of type is implementation-defined139), but shall be capable of representing the values of all the members of the enumeration.char The enumerated type is incomplete until immediately after the } that terminates the list of enumerator declarations, and complete thereafter. The enumeration member type upon completion is:
—
if all the values of the enumeration are representable as anint ; or,int 
— the enumerated typeFN1✨).
EXAMPLE The following fragment:
enum hue { chartreuse , burgundy , claret = 20 , winedark }; enum hue col , * cp ; col = claret ; cp = & col ; if ( * cp != burgundy ) /* ... */ makes hue the tag of an enumeration, and then declares col as an object that has that type and
as a pointer to an object that has that type. The enumerated values are in the set {0, 1, 20, 21}.cp 138)Thus, the identifiers of enumeration constants declared in the same scope are all required to be distinct from each other and from other identifiers declared in ordinary declarators.139)An implementation can delay the choice of which integer type until all enumeration constants have been seen.FN0✨)Therefore, a constraint has been violated.FN1✨)The integer type selected during processing of the enumerator list (before completion) of the enumeration may not be the same as the selected compatible implementation-defined integer type selected for the completed enumeration.
5.2.3. Add implementation-defined enumeration behavior to Annex J
6. Acknowledgements
Thanks to:
- 
     Aaron Ballman for help with the initial drafting; 
- 
     Aaron Ballman, Aaron Bachmann, Jens Gustedt & Joseph Myers for questions, suggestions and offline discussion; 
- 
     Joseph Myers, for detailed review of the many components and fine-tuning of the way enumeration constants are handled; 
- 
     Jens Gustedt, for detailed review and suggestions helping to shape this paper; 
- 
     Robert Seacord for editing suggestions; and, 
- 
     Clive Pygott for the initial draft of this paper. 
We hope this paper serves you all well.