C Tips: Generic Selection

C11 introduced generic selection expressions with the _Generic keyword. The general idea being the compiler can select an expression from a list of expressions to evaluate at compile time based on the type of it's first provided expression (commonly a variable).

An obvious use for this is polymorphic macros. For example if we wanted to make a generic DESCRIPTION() macro that gives us a string we can use for a given value:

const char *player_name(struct player *player);
const char *item_description(struct item *item);

#define DESCRIPTION(x) _Generic((x), \
    struct player *: player_name,
      struct item *: item_description)(x)

/* ... */

printf("description: %s\n", DESCRIPTION(thing)); /* we don't need to care what `thing` is now */

Another great use is the prevention of implicit integer conversions. Integer narrowing/truncation in particular which can result in information loss. While there are compiler flags such as -Wconversion and tools like clang-tidy that can catch these I find that I often do a naive cast to fix the warning and not the correct thing.

I ran into this recently with the compiler builtin ctz (count zeros).

int __builtin_ctz (unsigned int x)

If we provide a larger integer type such as an unsigned long long we'd get a truncation. This could be very bad as the truncated bits could contain info we need! Of course the compiler folks knew this so they provide sized variants:

int __builtin_ctzl (unsigned long)
int __builtin_ctzll (unsigned long long)

But it may not be apparent to a developer modifying this code that these variants exist. There's a decent chance they'd write a cast or even add extra code to check and make sure it's safe. Instead of assuming the developer has that knowledge or would take the time to look it up, we can make a macro that picks the correct variant for us:

#define CTZ(x) _Generic((x), \
        unsigned int: __builtin_ctz, \
        unsigned long: __builtin_ctzl, \
        unsigned long long: __builtin_ctzll \
    )(x)

Another handy use for this pattern is as a type assertion to prevent accidental conversions for just one case:

#define CTZ(x) _Generic((x), unsigned int: __builtin_ctz)(x)

Now if we provide a different type the compiler will complain even when conversion warnings are turned off. Nice!

Devlog Index