This paper aims to solve the problem often encountered with _Generic, where the expression of the association results in a constraint violation due to every association expression being checked even if it’s unselected.
typedef struct
{
char *chars;
size_t len;
} MyString;
#define string_length(s) \
_Generic(s, \
char *: strlen(s), \
MyString: (s).len)The string_length macro will never work, because each
association’s expression is checked:
char*, s.len is
invalid.MyString, strlen(s) is
invalid.The commonly done fix is to use a type coercion macro.
#define coerce(expr, T) \
_Generic(expr, T: expr, default: (T) {})
#define string_length(s) \
_Generic(s, \
char *: strlen(coerce(s, char *)), \
MyString: coerce((s), MyString).len)This works, but is repetitive and inconvenient, and leads to huge code sizes, making compile times slower.
This paper proposes adding a mechanism to optionally ignore the expression of a _Generic association if its type does not match the controlling operand.
#define string_length(s) \
_Generic(s, \
char *: [strlen(s)], \
MyString: [(s).len])The tokens inside the [] are only parsed as an
expression if the association is selected, they are skipped
otherwise.
If the generic association is default and uses
default: [expression] syntax, then it must be the last
association. This is to avoid having to backtrack and parse the default
expression if it turns out no other association matches.
Since the expression is surrounded by [], commas in the
expression don’t create ambiguity. So it is expression
instead of assignment-expression.
N3915 is another proposal that solves the same problem. This proposal tries a different approach: if the type does not match, don’t interpret the tokens as an expression. This has some benefits, it allows other assumptions to be made if the type matches. For example, if the first parameter has a specific type, things can be assumed about the other parameters’ types.
typedef struct ArenaAllocator ArenaAllocator;
typedef struct PoolAllocator PoolAllocator;
typedef struct
{
void *base;
size_t off;
} ArenaBlock;
typedef struct
{
unsigned index;
} PoolSlot;
void arena_rewind(ArenaAllocator *a, void *base, size_t off);
void pool_reclaim(PoolAllocator *p, unsigned index);
#define release(alloc, blk) \
_Generic(alloc, \
ArenaAllocator *: [arena_rewind(alloc, (blk).base, (blk).off)], \
PoolAllocator *: [pool_reclaim(alloc, (blk).index)])Here if release is passed an
ArenaAllocator*, then blk is
ArenaBlock, and if it’s PoolAllocator* then
blk is PoolSlot.
In N3915, the expressions may have to coerce the blk
argument to its expected type.
One problem of this proposal that N3915 doesn’t have is that the
expression in the [] can be invalid, and if the association
is not selected, the compiler won’t diagnose it. N3915 also avoids
repeated expansion of the macro parameter (because it rebinds it to a
different name), but if statement expressions or function literals are
added to the standard, then this proposal will be able to avoid multiple
expansions in most cases.
No prior art. However, I implemented it as a proof of concept in a fork of gcc 16
Based on N3886.
Add a production togeneric-association
in 6.5.2.1:
generic-association:
type-name : assignment-expression
default : assignment-expression
ignorable-generic-association
ignorable-generic-association:
type-name : [ expression ]
default : [ expression ]
type-name : [ balanced-token-sequence ]
default : [ balanced-token-sequence ]
Add a paragraph to Constraints in 6.5.2.1:
If a default generic association is an
ignorable-generic-association, then it shall be the last
generic association in the generic association list.
Add a paragraph to Semantics in 6.5.2.1:
If a generic-association is a non-default ignorable-generic-association:
type-name : [ expression ] and the expression between the
square brackets is the expression of the association.type-name : [ balanced-token-sequence ]And if it is a default ignorable-generic-association:
default : [ expression ] and the expression between the
square brackets is the expression of the association.default : [ balanced-token-sequence ]