DEV Community

Joshua Matthews
Joshua Matthews

Posted on

Named Params in C

Table of Contents:

Introduction

In a language like python you have the concept of **kwargs as a function parameter. This special double * syntax allows you to pass in named arguments captured as a dictionary, like the example below:

def print_item(**kwargs):
    # kwargs is a dictionary
    print(f"Item name: {kwargs['name']}")
    if "kind" in kwargs:
        print(f"Kind: {kwargs['kind']}")
    print(f"Quantity: {kwargs['quantity']}")
    print(f"Liters: {kwargs['liters']}")

print_item(name="Milk", quantity=2, liters=1)
# output:
# Item name: Milk
# Quantity: 2
# Liters: 1
Enter fullscreen mode Exit fullscreen mode

This allows for a convenient way to pass arguments by their name in any order you'd like and have optional parameters without complicating the function signature.

What if we could do this in C? (spoiler: there is a way)

Implementation in C

Through the magic of the preprocessor we can accomplish this effect, optional parameters and all.

First lets define our item structure.

struct item_t {
    const char *name;
    const char *kind;
    int quantity;
    float liters;
};
Enter fullscreen mode Exit fullscreen mode

Next we'll define our print function like you would normally in C.

// name it with `__t` for the concrete function.
void print_item__t(struct item_t item) {
    printf("Item name: %s\n", item.name);
    if (item.kind != NULL) {
        printf("Kind: %s\n", item.kind);
    }
    printf("Quantity: %d\n", item.quantity);
    printf("Liters: %f\n", item.liters);
}
Enter fullscreen mode Exit fullscreen mode

Now we can write the magically preprocessor part.

// name the macro what we want and use `...` to signify va_args
// Then call our function with a structure with default values
// and the va_args at the end to overwrite any values.
#define print_item(...)            \
    print_item__t((struct item_t){ \
        .name = "default",         \
        .kind = NULL,              \
        .quantity = 0,             \
        .liters = 0,               \
        __VA_ARGS__                \
    })
Enter fullscreen mode Exit fullscreen mode

So lets break down this macro.

  1. First we define the macro name and specify we are using va_args with ... as the parameter.
#define print_item(...)            \
Enter fullscreen mode Exit fullscreen mode
  1. Next we call the function with a structure of our item_t with default values.
    print_item__t((struct item_t){ \
        .name = "default",         \
        .kind = NULL,              \
        .quantity = 0,             \
        .liters = 0,               \
Enter fullscreen mode Exit fullscreen mode
  1. Last we pass in the __VA_ARGS__ values at the end of our default structure. This works because C allows the later defined properties to overwrite earlier ones.
        .liters = 0,               \
        __VA_ARGS__                \
    })
Enter fullscreen mode Exit fullscreen mode

Here's how all of this would be structured in actual files.

  • item.h
#ifndef ITEM_H
#define ITEM_H

struct item_t {
    const char *name;
    const char *kind;
    int quantity;
    float liters;
};

void print_item__t(struct item_t item);

#define print_item(...)            \
    print_item__t((struct item_t){ \
        .name = "default",         \
        .kind = NULL,              \
        .quantity = 0,             \
        .liters = 0,               \
        __VA_ARGS__                \
    })

#endif
Enter fullscreen mode Exit fullscreen mode
  • item.c
#include "item.h"
#include <stdio.h>

void print_item__t(struct item_t item) {
    printf("Item name: %s\n", item.name);
    if (item.kind != NULL) {
        printf("Kind: %s\n", item.kind);
    }
    printf("Quantity: %d\n", item.quantity);
    printf("Liters: %f\n", item.liters);
}
Enter fullscreen mode Exit fullscreen mode

Using our Implementation

Now we can use our macro similar to the python example in the introduction.

#include "item.h"

int main(void) {
    print_item(.name = "Milk", .quantity = 2, .liters = 1);
    return 0;
}
// output:
// Item name: Milk
// Quantity: 2
// Liters: 1
Enter fullscreen mode Exit fullscreen mode

Conclusion

Through preprocessor magic we can achieve a syntax convenience with calling functions that have struct parameters as options. This can be extremely useful for functions with lots of optional parameters and\or default values. However, preprocessor magic is something that is either loved or hated so you may want to consider if it's worth implementing in your project or not.

Top comments (1)

Collapse
 
lolpopgames profile image
LolPopGames

You made a small mistake with the comments at the end (instead of // or /* you used #)

I really liked the idea of ​​making parameters optional, even though I know you mentioned creating a Python-like way to interact with parameters

This method allows for optional parameters, and if you think about it further, you can implement mandatory parameters as well


Speaking of C syntax tricks, I once played around with using the dot "similar to how namespaces are used."

My favorite example is a base64 library:

char *__b64__encode(const uint8_t *bytes, size_t size);
uint8_t *__b64__decode(const char *b64_str, size_t *decoded_size);

struct __b64
{
    char *(*encode)(const uint8_t *, size_t);
    uint8_t *(*decode)(const char *, size_t *);
};

#define b64 (struct __b64) { \
    .encode = &__b64__encode, \
    .decode = &__b64__decode, \
}
Enter fullscreen mode Exit fullscreen mode

And now we can use b64.encode and b64.decode that looks cool