Home > Net >  Wrapping variadic arguments in a c macro
Wrapping variadic arguments in a c macro

Time:11-07

So, I want to make a function(-like macro) that takes any number of arguments of different types and does something to it. I mean, I did manage to make it work, but I'm looking for a more elegant solution (or to make sure my way is the way it should look like).

Example code of a function macro print(...):

#ifndef EVIL_PRINT_H
#define EVIL_PRINT_H

#include <stdio.h>

#define TWENTY_SECOND_ARGUMENT(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, ...) _22

#define COUNT_ARGUMENTS(...) TWENTY_SECOND_ARGUMENT(__VA_ARGS__, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)

#define PRINT_CHAR_EVIL(x) printf("%c", x)
#define PRINT_INT_EVIL(x) printf("%i", x)
#define PRINT_FLOAT_EVIL(x) printf("%f", x)
#define PRINT_DOUBLE_EVIL(x) printf("%d", x)
#define PRINT_PTR_EVIL(x) printf("%p", x)
#define PRINT_STR_EVIL(x) printf("%s", x)

#define PRINT_ONE_EVIL(x, ...) _Generic(x, \
            char: PRINT_CHAR_EVIL(x), \
            int: PRINT_INT_EVIL(x), \
            float: PRINT_FLOAT_EVIL(x), \
            double: PRINT_DOUBLE_EVIL(x), \
            void *: PRINT_PTR_EVIL(x), \
            char const *: PRINT_STR_EVIL(x), \
            char *: PRINT_STR_EVIL(x) \
            )
#define PRINT_TWO_EVIL(_1, _2, ...) PRINT_ONE_EVIL(_1); PRINT_ONE_EVIL(_2)
...
#define PRINT_TWENTY_ONE_EVIL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, ...) PRINT_TWENTY_EVIL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20); PRINT_ONE_EVIL(_21)

#define print(...) do { \
    switch (COUNT_ARGUMENTS(__VA_ARGS__)) { \
        default:break; \
        case 1: \
        PRINT_ONE_EVIL(__VA_ARGS__); \
        break; case 2: \
        PRINT_TWO_EVIL(__VA_ARGS__, 2); \
... \
        break; case 21: \
        PRINT_TWENTY_ONE_EVIL(__VA_ARGS__, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18, 19, 20, 21); \
    } \
    } while(0);


#endif

My problem with this approach is that it copies a lot of code for one call, but I don't know, maybe the compiler optimizes all unneeded branches out. Also one limitation is that it will not accept more than x (in my case - 21) number of arguments. Adding more arguments is no big deal, but the file size is going to grow, if you need like 100 arguments.

Usage example:

#include "evil_print.h"

int main(void) {
    struct {
        int i;
        char c;
    } v = {.i = 100, .c = 'A'}, o;
    o = v;
    print(v.i, ", ", o.c);
}

Yes, I know that it is easy in c , don't mention that language here.

CodePudding user response:

I'm looking for a more elegant solution

My most elegant solution, that I finally ended using for https://gitlab.com/Kamcuk/yio/ library, was to pass an array of function pointers that depend on the type of arguments. Such an array can be constructed inside the macro expansion with a compound literal and then passed to a dispatcher function. Because you are allowed to pass va_list via a pointer to the functions and then work on it, you can do type-specific operations inside the function pointers. The design is for limiting the use of preprocessor to the minimum, dispatching to function C side as early as possible.

#include <stdarg.h>
#include <stdio.h>
#include <limits.h>

#define APPLYFOREACH_1(f,a)  f(a)
#define APPLYFOREACH_2(f,a,...)  f(a) APPLYFOREACH_1(f,__VA_ARGS__)
#define APPLYFOREACH_3(f,a,...)  f(a) APPLYFOREACH_2(f,__VA_ARGS__)
#define APPLYFOREACH_4(f,a,...)  f(a) APPLYFOREACH_3(f,__VA_ARGS__)
#define APPLYFOREACH_N(_4,_3,_2,_1,N,...)\
        APPLYFOREACH##N
#define APPLYFOREACH(f, ...) \
        APPLYFOREACH_N(__VA_ARGS__,_4,_3,_2,_1)(f, ##__VA_ARGS__)

// ---------------------------------------------
// The main logic dispatcher.

typedef int (*lib_print_t)(va_list *va);

int lib_print_in_2(const lib_print_t *printers, va_list *va) {
    int result = 0;
    for (; *printers != NULL;   printers) {
        const int tmp = (*printers)(va);
        if (tmp < 0) return tmp;
        result  = tmp;
    }
    return result;
}

int lib_print_in(const lib_print_t *printers, ...) {
    va_list va;
    va_start(va, printers);
    const int ret = lib_print_in_2(printers, &va);
    va_end(va);
    return ret;
}

// ---------------------------------------------
// Type specific printers.

int lib_print_int(va_list *va) {
    int c = va_arg(*va, int);
    return printf("%d", c);
}

int lib_print_char(va_list *va) {
    char c = va_arg(*va,
        // char is promoted to int.... or is it?
        // There are _many_ such cases to handle.
#if CHAR_MAX > INT_MAX
        unsigned int
#else
        int
#endif
    );
    return printf("%c", c);
}

int lib_print_charp(va_list *va) {
    const char *c = va_arg(*va, char *);
    return printf("%s", c);
}

int lib_print_float(va_list *va) {
    // but float _is_ promoted to double
    float c = va_arg(*va, double);
    return printf("%f", c);
}

#define DISPATCH(x) \
    _Generic((x) \
    , int: lib_print_int \
    , char: lib_print_char \
    , char*: lib_print_charp \
    , const char *: lib_print_charp \
    , float: lib_print_float \
    /* Note - comma on the end for below */ \
    ),

// ---------------------------------------------

// Calls lib_print_in with an array of function pointers
// and arguments.
#define lib_print(...) \
        lib_print_in( \
            (const lib_print_t []){ \
                APPLYFOREACH(DISPATCH, __VA_ARGS__) \
                NULL \
            }, \
            ##__VA_ARGS__ \
        )

// ---------------------------------------------

int main() {
    lib_print(1, ", ", 1.0f, (char)'\n');
}

The code outputs 1, 1.000000.

Why do you pass function pointers instead of calling them right away?

To not abuse processor, so it nicely expands to a single function call, accumulate the result, correctly handle error, offer additional features like format string parsing. But sure, you can just call them right away if you do not care about such stuff. You would just do plain _Generic with a FOREACH macro, that's all.

#include <stdio.h>

#define APPLYFOREACH_1(f,a)  f(a)
#define APPLYFOREACH_2(f,a,...)  f(a) APPLYFOREACH_1(f,__VA_ARGS__)
#define APPLYFOREACH_3(f,a,...)  f(a) APPLYFOREACH_2(f,__VA_ARGS__)
#define APPLYFOREACH_4(f,a,...)  f(a) APPLYFOREACH_3(f,__VA_ARGS__)
#define APPLYFOREACH_N(_4,_3,_2,_1,N,...)\
        APPLYFOREACH##N
#define APPLYFOREACH(f, ...) \
        APPLYFOREACH_N(__VA_ARGS__,_4,_3,_2,_1)(f, ##__VA_ARGS__)

int lib_print_int(int c) {
    printf("%d", c);
}

void lib_print_char(char c) {
    printf("%c", c);
}

void lib_print_charp(const char *c) {
    printf("%s", c);
}

void lib_print_float(float c) {
    printf("%f", c);
}

#define lib_print_one(x) \
    _Generic((x) \
    , int: lib_print_int \
    , char: lib_print_char \
    , char*: lib_print_charp \
    , const char *: lib_print_charp \
    , float: lib_print_float \
    )(x);

#define lib_print(...)  do { \
        APPLYFOREACH(lib_print_one, __VA_ARGS__) \
    } while(0)

int main() {
    lib_print(1, ", ", 1.0f, (char)'\n');
}

Note: _Generic is meant to be used like _Generic(x, int: functionpointers1, double: functionpointer2)(arguments, argument2). If you do _Generic(x, char: printf("%c", c) you should get a lot of compiler warnings of mismatched format string. It is easier with function pointers. In this case, you could also expand to printf(_Generic(x, char: "%c", int: "%d"), x), but I still like function pointers as much cleaner.

  • Related