Graham King

Solvitas perambulum

Keyword arguments in C

software c
Summary
In C99, you can use a combination of compound literals, designated initializers, and variadic macros to create cleaner and more flexible function calls with keyword arguments. For example, by defining a macro that converts keyword arguments into a structured form, you can call a function like `my_func(.username="Bob", .is_admin=true);`. This approach uses compound literals to create a temporary structure, designated initializers to pick specific structure members to initialize, and variadic macros to handle variable argument lists. This allows for optional keyword arguments with default values, as unspecified arguments default to zero or null, while repeated arguments use the last specified value.

This is valid C nowadays:

my_func(.username="Bob", .is_admin=true);

I found it in 21st Century C. It requires a macro and a structure, and relies on three features introduced in C99.

#include <stdio.h>      // printf
#include <stdbool.h>    // bool - there's a bool type now

// Macro that turns the kwargs into an struct
#define my_func(...) my_func_base(\
    (struct user){.is_admin=false, __VA_ARGS__});

struct user {
    char *username;
    bool is_admin;
};

// The actual function - and yes there's single line comments too
void my_func_base(struct user u) {
    printf("Hello %s\n", u.username);
}

int main(int argc, char *argv[]) {
    my_func(.username="Bob", .is_admin=true);
}

The three new features introduced in C99 that make this possible are:

  • Compound literals which allow my_user = (user) {"Bob", true}.
  • Designated initializers which give us struct user my_user = {.username="Test", .is_admin=true}
  • Variadic macros which allow #define‘s to take ... as a parameter and have it substituted wherever __VA_ARGS__ appears in that macro.

All the keyword arguments are optional, because compound literal rules state that any unspecified arguments are set to zero / null of the appropriate type.

We even have default arguments, like the .is_admin=false in the macro above, because designated initializer rules state that if the argument is repeated, the last argument wins.