Proposal for C2y/C3x

Abstract

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.

Rationale

Example 1

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:

The commonly done fix is to use a type coercion macro.

Example 2

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

Proposal

This paper proposes adding a mechanism to optionally ignore the expression of a _Generic association if its type does not match the controlling operand.

Example 3

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

Alternative Approach

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.

Example 4

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.

Prior Art

No prior art. However, I implemented it as a proof of concept in a fork of gcc 16

Proposed Wording

Based on N3886.

Add a production to generic-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:

And if it is a default ignorable-generic-association:

References