1. Changelog
1.1. Revision 0 - December 16th, 2025
-
Initial release. ✨
2. Introduction and Motivation
selection will produce an error when trying to actually use the input value in a manner that is not plausible with all branches. This typically comes up when using macros that allow repeated use of the created generic selection and its expression. For example, this code:
int use_int_ptr ( int * pi ); int use_dbl_ptr ( double * pd ); #define USE_THING(THING) _Generic(&(typeof(THING)){ THING }, \ int*: use_int_ptr(&v), \ double*: use_dbl_ptr(&v), \ default: 0 \ ) int main () { int c = 0 ; int v = USE_THING ( c ); return v ; }
will spit out this error from latest trunk GCC (December 16th, 2025):
source>: Infunction 'main' : <source>:6:30: error: passing argument1 of'use_dbl_ptr' from incompatible pointer type[ -Wincompatible-pointer-types] 6 | double*: use_dbl_ptr( & v) ,\ | ^~| | | int * <source>:12:17: note:in expansion of macro'USE_THING' 12 | intv = USE_THING( c) ; | ^~~~~~~~~ <source>:2:25: note: expected'double *' but argument is of type'int *' 2 | int use_dbl_ptr( double* pd) ; | ~~~~~~~~^~ <source>:6:33: error: expected')' before':' token6 | double*: use_dbl_ptr( & v) :\ | ^ <source>:12:17: note:in expansion of macro'USE_THING' 12 | intv = USE_THING( c) ; | ^~~~~~~~~ <source>:4:34: note: to match this'(' 4 | #define USE_THING(THING) _Generic(&(typeof(THING)){ THING }, \ | ^ <source>:12:17: note:in expansion of macro'USE_THING' 12 | intv = USE_THING( c) ; | ^~~~~~~~~
(Godbolt.)
In the case above, one can just cast a pointer to and then cast to the proper type to get rid of the errors, but this does not generalize when performing other operations on the input expressions. Additionally, can have nested macro problems, provoking multiple token expansions and replacements that can significantly increase compile-times.
A generic solution is putting a secondary that effectively "launders" the input into the correct type after the expression, a technique Martin Uecker calls (short for contravariant transform). This allows the use of two completely disparate types (e.g., an integer versus a structure) without provoking an error:
#include <stdio.h>#define contrav(T, x) _Generic(typeof(x), T: (x), default: (T){ }) #define info(x) \ _Generic(x, \ int: printf("%d", contrav(int, x)), \ struct foo: printf("%s", contrav(struct foo, x).name) \ ) struct foo { char * name ; }; int main () { struct foo f = { "test" }; info ( 3 ); info ( f ); // prints: // 3 // test }
(Godbolt.) Unfortunately, this deepens the issue of multiple recursive expansions of a macro and still leaves open the potential of having an expression used multiple difference times. It also requires the user understand the technique that performs, which most programmers absolutely do not know how to do given that an extremely frequent request for is the ability to "ignore untaken branches". Unfortunately, one of the chief benefits of all of a generic selection’s branches is that they are type-checked, which is different from the way C++ templates and similar generic constructs behave.
2.1. A Bigger Problem Than Hoped
This is not the first time this problem has been brought up. In fact, it’s come up hundreds of times as people attempted to use to code relatively simply things. One such person, Simon Tatham, wrote a long article circa 2023, which covers in full the issues. It even proposes solutions, but only after going through a full archaeological dig on the proposals, previous existing practice, and problems of that WG14 encountered when C11’s Generic Selection was first being standardized. They rediscovered why it was the way it was in certain cases, but still felt that certain parts of it were effectively defects or WG14 not thinking clearly in the full design.
2.2. A More Thorough Solution
A viable solution for expression-based is to allow the creation of an l-value or an r-value of the right type based on the input of a selection that uses an controlling expression rather than a controlling type. This allows all branches to be type-checked, but checked with a type that is appropriate rather than allowing a single use of a generic macro or similar to completely and utterly destroy any hope at proper compilation.
This paper proposes adding declaration-name as one of the new productions of generic-association, which will add an as the name of the inserted controlling expression as an r-value or an l-value. It also modifies the rules of a controlling expression to match against both the exact type first (to allow for an l-value expression that may be qualified to be selected) and then the type after typical l-value conversion strips the qualifiers and does other removals.
3. Design
The design is simple. There will be three productions for a generic-association grammar term rather than just two, alongside a change for the branch, to allow for an identifier of the proper type, like so:
generic-association:
type-name
assignment-expression: declaration-name
assignment-expression:
identifieroptdefault assignment-expression: declaration-name:
declaration-specifiers declarator
The use of declaration-name is primarily to allow a non-optional identifier to appear in the grammar in the usual expected place, with type-name being the same just without the identifier in its grammar. That makes the previously shown code samples look as follows:
int use_int_ptr ( int * pi ); int use_dbl_ptr ( double * pd ); #define USE_THING(THING) _Generic(THING, \ int pi: use_int_ptr(pi), \ double pd: use_dbl_ptr(pd): \ default: 0 \ ) int main () { int c = 0 ; int v = USE_THING ( c ); return v ; }
#define info(x) _Generic(x, \ int d: printf("%d\n", d), \ struct foo s: printf("%s\n", s.name) \ ) struct foo { char * name ; }; int main () { struct foo f = { "test" }; info ( 3 ); info ( f ); }
This allows us to have access to the result of the generic selection’s controlling expression. Additionally, the scope of the introduced is from the expression-introducing colon to the terminating comma . This keeps things finely scoped. If ISO C had statement expressions, we could state that it is as-if there is a statement expression that introduces this variable and surrounds the of the generic selection.
Additionally, right now storage-class specifiers on parameter declarations are ignored. Rather than ignoring them, we plan to make it a constraint violation for to have e.g. or or similar. If as a type specifier enters into C2y, then we expect to add as being a constraint violation here, too.
3.1. Existing Practice: Allowing Modification / L-values
An important part of upholding existing practice is to allow for modification through an l-value. Using example code from Martin Uecker that shows modification working, the following code are things that plausibly exist in the real world already:
#include <stdio.h>#define contrav(T, x) _Generic(typeof(x), T: (x), default: (T){ }) #define bar(x) _Generic(x, \ int: contrav(int, x), \ struct foo: contrav(struct foo, x).name \ ) struct foo { char * name ; }; int main () { struct foo f = { "test" }; bar ( 3 ); // 3 bar ( f ) = "something" ; // f.name puts ( f . name ); // prints "something" }
(Godbolt). Therefore, it is important that this code, when transferred to the next syntax, also works. The proposed look and semantics would be as so:
#include <stdio.h>#define bar(x) _Generic(x, \ int v: v, \ struct foo v: v.name \ ) struct foo { char * name ; }; int main () { struct foo f = { "test" }; bar ( 3 ); bar ( f ) = "something" ; puts ( f . name ); // prints "something" }
Effectively, if the input value into a generic selection expression would be an lvalue, then it remains as such after a generic association branch is chosen. It applies similarly if it is an r-value. Because generic selection is an expression, the use of any created temporary values will always last for the duration of the "full expression", and so this should not cause any lifetime issues at all in the confines of both existing code and newly written code.
This sort of "write through" capability was present since the original feature in C11, though the primary focus was obviously on making sure code did not error when trying to make type-generic macros:
#include <string.h>int one ( int a ) { return a ; } int two ( int a , int b ) { return a + b ; } int three ( int a , int b , int c ) { return a + b * c ; } int main () { int x = 0 ; return x + _Generic ( x , int : one , double : two , const char *: three )( 1 ); }
(Godbolt). Therefore, we seek to preserve what has already been a feature since this was conceived in the middle of the C11 cycle.
3.2. Evaluation of the Expression
Currently, the controlling operand of a generic selection expression is not evaluated if it’s an expression. In the case of a generic association that uses the declaration-name form rather than the type-name form, the expression is only evaluated after selection chooses a branch that has the declaration-name form. This will prevent the addition of new generic associations within a list of existing generic associations forcing unexpected evaluations that were not previously considered. It is also only evaluated once, even if the identifier provided is used multiple times. Therefore, under the new feature, the following code returns from :
int result = 0 ; int change_global ( void ) { result += 3 ; return 2 ; } int main ( void ) { int one = _Generic ( change_global (), // not evaluated int : 1 , double : 0xBAD , default : 0xBAD ); int two = _Generic ( change_global (), // evaluated int a : ( a * a ) / 2 , // only evaluated once double : 0xBAD , default : 0xBAD ); int three = _Generic ( change_global (), // not evaluated double : 0xBAD , default : 3 ); int four = _Generic ( change_global (), // not evaluated double d : ( int ) d + 0xBAD , default : 4 ); int five = _Generic ( change_global (), // evaluated int a [[ maybe_unused ]] : 5 , // evaluated even if `a` is unused double : 0xBAD , default : 0xBAD ); if ( one != 1 ) { return 1 ; } if ( two != 2 ) { return 2 ; } if ( three != 3 ) { return 3 ; } if ( four != 4 ) { return 4 ; } if ( five != 5 ) { return 5 ; } if ( result != 9 ) { return 9 ; } return 0 ; }
3.3. Direct Type Matching for Expressions
One of the problems with expressions is that it undergoes "l-value conversion" (qualifiers stripped, arrays converted to pointers, and similar) before the generic branch is matched. The problem was partially fixed by directly matching on an existing type, introduced by Aaron Ballman earlier into C2y. That allows directly matching on types that are e.g. -qualified, without having to make a pointer out of it.
The problem comes back again when we want to have that exact matching but still want to produce a named identifier for whatever expression has gone in The use of type-name as the generic-controlling-operand means that there is no expression to evaluate and therefore nothing to set the expression to. Therefore, with an expression, one would need to morph the input expression to have a different type than what is put in, just to be able to match qualifiers or arrays. This, of course, presents the immediately problem that this is creating a new expression of a different type than what is intended, resulting in unneeded temporaries or other issues.
It also presents a problem when qualifiers are ignored but it matters to the input, where interacting with an e.g. is not the same as interacting with an :
#include <stdio.h>#define read_maybe_update(x) _Generic(x, \ int v: (printf("%d was read and updated\n", (v += 1)), v), \ const int v: (printf("%d was read\n", v), v) \ ) int main () { const int var = 0 ; return read_maybe_update ( var ); // return 0; }
If we could only match on in this scenario (as is the case currently, today, with expression-based matching), we would immediately attempt to pick the branch, and thus attmept to assign to a integer. This would do the wrong thing, as would have the wrong actual backing type (, not ).
One could argue that simply disallowing this kind of behavior in general would be wiser (by, e.g., backing these declarations with new variables whose types are and effectively removing this use case), but that would be a strict reduction in power (as demonstrated by § 3.1 Existing Practice: Allowing Modification / L-values).
3.4. Identifier Scope and Lifetime
The scope of the identifier introduced by this is from the and to the terminating . This is different from every other kind of identifier, so it needs a special carve out in §6.2.1 Scopes. We’re calling it generic association scope, for now. Just like labels, they’re a one-use-case kind of variable. This is just going to be called "generic lifetime" for the time being, because that’s the simplest possible way to call it. If we had statement expressions, we could simply say the scope is as-if the identifier was declared at the top of a statement expression that surrounds the assignment-expression that comes after the and before the , like so:
int r = 0 ; _Generic ( r , int v : v * v + 35 / ( v += 2 ) )
becomes...
int r = 0 ; _Generic ( r , int : ({ int * __lvalue_v = & r ; ( * __lvalue_v ) * ( * __lvalue_v ) + 35 / (( * __lvalue_v ) += 2 ); }) )
Unfortunately, we do not have statement expressions so we have to spend wording specifying something for this. Hopefully, once we get statement expressions, we can do away with the wording and just say it’s as-if a statement expression with normal block semantics exists in that space.
The lifetime of the backing storage for the identifier is regular automatic storage duration.
As an example, the identifier can be used for all of the generic association branches because the identifier’s scope does not bleed or leak out of the assignment-expression portion of that branch:
#include <stdio.h>#define bar(x) _Generic(x, \ int v: v, /* first use */ \ struct foo v: v.name /* perfectly fine reuse */ \ ) struct foo { char * name ; }; int main () { struct foo f = { "test" }; bar ( 3 ); bar ( f ) = "something" ; puts ( f . name ); // prints "something" }
3.5. Other Potential Syntaxes
We settled on simply adding the declaration-name branch to the generic-association syntax because it is, as we understand it, the least problematic and easiest to handle. It does present some issues with comprehension, unfortunately: the use of a declaration-like syntax implies that a new variable is being created, which doesn’t feel proper with the ability of generic selection to yield an l-value based on what the resulting expression computes.
There are other ways to introduce the identifier in the sequence.
One possible choice is to use a real introducer for the identifier, so it doesn’t look like a declaration-name. An example of this is with a , so that the type-name is separated from the identifier, as in: . Other than being conflicting with C++-style syntax of , it also seems a bit unnecessary given the solution we’ve settled on. It might get rid of the disambiguation rule that anything that can be parsed as a type-name should be parsed as a type-name rather than a declaration-name, where possible. Other than this, there are little benefits, unfortunately.
It could also be something along the lines of for a generic association branch, but a single does not really provide enough information to the parser to know if the following tokens will be the special new syntax versus just the existing syntax where is meant to be parsed as an . One could delay the parsing and decide whether or not its either the new syntax or the old syntax by waiting for the first non-parenthesized from showing up and then deciding how to interpret two top-level colons, but it can be hard to deal with given the use of e.g. nakedly as part of an assignment-expression. (This can make it such that the compiler might have to consider and identifier and an error before the portion, for example.)
A separator between the type name and the identifier could strengthen the idea that the matching type may not exactly be the same as the input expression, as well, since that’s how generic with expressions currently works (after the strict-matching capabilities added by this proposal’s wording).
4. Prior Art
Other than the "contravariant transformation" detailed in § 2 Introduction and Motivation, there is no direct prior art for this feature in compilers as a language feature.
5. Wording
The wording is relative to the latest Working Draft at time of publication, [n3685].
5.1. Intent
The intent of this wording is to provide:
-
an optional identifier to a generic association branch on the left hand side before the
;: -
an optional identifier to a
generic association branch on the left hand side before thedefault ;: -
both strict type matching and post-lvalue-conversion matching for generics that use a controlling operand where possible;
-
the evaluation of the input expression if and only if a generic branch with an identifier actually matches;
-
and, the ability to potentially modify an l-value if the identifier if the expression produces an l-value.
5.2. Modify "§6.2.1 Scopes of identifiers"
6.2.1 Scopes of identifiersAn identifier can denote:
a standard attribute, an attribute prefix, or an attribute name;
an object;
a function;
a tag or a member of a structure, union, or enumeration;
a typedef name;
- a generic association name;
a label name;
a macro name;
or a macro parameter.
The same identifier can denote different entities at different points in the program. A member of an enumeration is called an enumeration constant. Macro names and macro parameters are not considered further here, because prior to the semantic phase of program translation any occurrences of macro names in the source file are replaced by the preprocessing token sequences that constitute their macro definitions.
For each different entity that an identifier designates, the identifier is visible (i.e. can be used) only within a region of program text called its scope. Different entities designated by the same identifier either have different scopes or are in different name spaces. There are
fourfive kinds of scopes: function, file, block, generic association, and function prototype. (A function prototype is a declaration of a function.)A generic association name is the only kind of identifier that has generic association scope. This scope begins at the
after the declaration name or: identifier and ends at the terminatingdefault or, of that generic selection’s generic association (6.5.2.1).) ...
5.3. Modify §6.5.2.1 Generic selection
📝 EDITOR’S NOTE: The constraints text of the generic selection section is moved into three paragraphs to break it up. 📝
6.5.2.1 Generic selectionSyntaxgeneric-selection:
_Generic generic-controlling-operand( generic-assoc-list, ) generic-controlling-operand:
assignment-expression
type-name
generic-assoc-list:
generic-association
generic-assoc-list
generic-association, generic-association:
type-name
assignment-expression: - declaration-name
assignment-expression:
identifieroptdefault assignment-expression: declaration-name:
declaration-specifiers declarator
DescriptionThe controlling type of a generic selection is the type or set of types used to select a branch in the list of generic associations in the generic selection. The generic association type is the type indicated by a generic association’s type name or declaration name, if present. The generic association name is either the identifier in the declaration name or the identifier after
, if present.default ConstraintsA generic selection shall have no more than one
generic association. No two generic associations in the same generic selection shall specify compatible types.default If the generic controlling operand is an assignment expression, the controlling type of the generic selection expression is the type of the assignment expression as if it had undergone an lvalue conversion,FN) array to pointer conversion, or function to pointer conversion. Otherwise, the controlling type of the generic selection expression is the type designated by the type name. The controlling type shall be compatible with at most one of the types named in the generic association list. If a generic selection has no default generic association, its controlling type shall be compatible with exactly one of the types named in its generic association list.The controlling type shall be compatible with at most one of the types named in the generic association list. If a generic selection has no default generic association, its controlling type shall be compatible with exactly one of the types named in its generic association list.
If an identifier can be treated either as a typedef name or as a generic association name, it shall be taken as a typedef name.
There shall be no storage-class specifiers on a declaration name, if present.
SemanticsIf the generic controlling operand is an assignment expression, the controlling type of the generic selection expression is first the type of the expression. If no generic association type has a compatible type with this first type, then the controlling type of the generic selection expression is as if it had undergone an lvalue conversion,FN) array to pointer conversion, or function to pointer conversion.
Otherwise, if the generic controlling operand is a type name, the controlling type of the generic selection expression is the type designated by the type name.
The generic controlling operand, sizeSize expressions,and typeof operators contained in the type names of generic associations are not evaluated.If a generic selection has a generic association with a type name that is compatible with the controlling type, then the result expression of the generic selection is the expression in that generic association. Otherwise, the result expression of the generic selection is the expression in the
generic association. None of the expressions from any other generic association of the generic selection is evaluated. The generic association whose expression is evaluated is the selected generic association.default The generic controlling operand is evaluated if and only if both the generic controlling operand is an assignment expression and the selected generic association has a generic association name. Otherwise, it is not evaluated.
If present, the generic association name has the value of the generic controlling operand’s expression and is an lvalue, a function designator, or a void expression if the generic controlling operand is. Its type is either:
the generic association type if it is not the selected generic association;
or, the generic controlling operand’s type.
NOTE If evaluated, the generic controlling operand’s expression is evaluated once and only once, regardless of the number of times the generic association name is used or if its even used at all.
The type and value of a generic selection are identical to those of its result expression. It is an lvalue, a function designator, or a void expression if its result expression is, respectively, an lvalue, a function designator, or a void expression.
EXAMPLE A cbrt type-generic macro can be implemented as follows:
#define cbrt(X) _Generic((X), \ long double: cbrtl, \ default: cbrt, \ float: cbrtf \ )(X) EXAMPLE The following two generic selection expressions select different associations because the assignment expression operand undergoes lvalue conversion while the type name operand is unchanged:
int func ( const int i ) { return _Generic ( i , int : 0 , // int is selected const int : 1 , default : 2 ) + _Generic ( typeof ( i ), int : 0 , const int : 1 , // const int is selected default : 2 ); } Thus, this function returns
.1 EXAMPLE The following two generic selection expressions select the same associations:
int func ( const int i ) { return _Generic ( i , int : 0 , const int : 1 , // const int is selected default : 2 ) + _Generic ( typeof ( i ), int : 3 , const int : 4 , // const int is selected default : 5 ); } Thus, this function returns
.5 EXAMPLE The following generic selection expressions are valid and all evaluate to 1.
void foo ( int n , int m ) { _Generic ( int [ 3 ][ 2 ], int [ 3 ][ * ] : 1 , int [ 2 ][ * ] : 0 ); _Generic ( int [ 3 ][ 2 ], int [ * ][ 2 ] : 1 , int [ * ][ 3 ] : 0 ); _Generic ( int [ 3 ][ n ], int [ 3 ][ * ] : 1 , int [ 2 ][ * ] : 0 ); _Generic ( int [ n ][ m ], int [ * ][ * ] : 1 , char [ * ][ * ] : 0 ); _Generic ( int ( * )[ 2 ], int ( * )[ * ] : 1 ); } EXAMPLE The following generic selection within a macro appropriately evaluates the incoming expression and uses it to print out information.
#include <stdio.h>#define info(x) _Generic(x, \ int d: printf("[info] %d\n", d), \ struct foo s: printf("[info] %s\n", s.name) \ default v: printf("[info] %p\n", (void*)&v) \ ) struct foo { char * name ; }; double get_d ( void ) { return 0.5 ; } int main () { struct foo f = { "test" }; info ( 3 ); info ( f ); info ( get_d ()); } EXAMPLE If the incoming operand of a generic selection expression is a modifiable lvalue, then such an expression can be used for modification.
#include <stdio.h>#define bar(x) _Generic(x, \ int v: v, \ struct foo v: v.name \ ) struct foo { char * name ; }; int main () { struct foo f = { "test" }; bar ( 3 ); bar ( f ) = "something" ; puts ( f . name ); // prints "something" } EXAMPLE The generic controlling operand has the type of the expression itself when the branch is selected, which does not have to be exactly the generic association type due to lvalue conversions.
int main () { const int var = 0 ; return _Generic ( var , int v : v += 1 ); // constraint violation: // assignment with `const` object }