Home > Software engineering >  Function overloading in C with _Generic when __VA_ARG__ can be empty
Function overloading in C with _Generic when __VA_ARG__ can be empty

Time:10-21

I am looking to use the _Generic preprocessor directive to achieve function overloading. I learned to use it from this wonderfully detailed answer.

However, it doesn't seem to cover this case:

#include <stdio.h>

void foo_one(int);
void foo_two(int, float*);

#define FIRST_VARG(_A, ...)     _A
#define foo(_X, ...)            _Generic(   (FIRST_VARG(__VA_ARGS__,)), \
                                            float*      : foo_two,      \
                                            default     : foo_one)  (_X, __VA_ARGS__)

void foo_one(int A)
{
    printf("FOO ONE: %d\n", A);
}

void foo_two(int A, float* B)
{
    printf("FOO TWO: %d, %f", A, *B);
}

void main()
{
    float x = 3.14;
    float* y = &x;
    foo(1); // This statement pops an error
    foo(2, y);
}

Here, you can see that the first argument to both functions is an integer. However, the second argument of the second function is a float*. Visual Studio complains about the calling foo(1), but not when calling foo(2, y). The error is

error C2059: syntax error: ')'

I know Visual Studio can support _Generic with a small trick. So, I feel like there is something I am doing wrong. There is a comment in the answer where I learned about _Generic that suggests using (SECOND(0, ##__VA_ARGS__, 0), etc. But I don't understand it.

Can someone walk me through how I could achieve my intended result?

CodePudding user response:

There are two issues. First is selecting the second argument of foo for generic selection in the case when there is no second argument.

Other is #define foo(_X, ...) which will not work for foo(1) because the function macro expect two or more arguments. It often works but it a compiler specific extensions. Compiling in pedantic mode will raise a warning. See https://godbolt.org/z/z7czvGvbc

A related issue is expanding to (_X, __VA_ARGS__)which will not work for foo(1) where ... maps to nothing.

The both issues can be addressed with placing a dummy type (NoArg) at the end of the list prior to extracting the second argument. It will both extend the list and add a value that can be used by _Generic to correctly dispatch the function expression.

#include <stdio.h>

void foo_one(int);
void foo_two(int, float*);

typedef struct { int _; } NoArg;
// use compound literal to form a dummy value for _Generic, only its type matters
#define NO_ARG ((const NoArg){0})

#define foo_(args, a, b, ...) \
  _Generic((b)                \
           ,NoArg: foo_one    \
           ,default: foo_two  \
           ) args

// pass copy of args as the first argument
// add NO_ARG value, only its type matters
// add dummy `~` argument to ensure that `...` in `foo_` catches something
#define foo(...) foo_((__VA_ARGS__), __VA_ARGS__, NO_ARG, ~)

void foo_one(int A)
{
    printf("FOO ONE: %d\n", A);
}

void foo_two(int A, float* B)
{
    printf("FOO TWO: %d, %f\n", A, B ? *B : 42.0f);
}

#define TEST 123

int main(void)
{
    float x = 3.14;
    float* y = &x;
    foo(1); // This statement pops an error
    foo(2, y);
    foo(TEST, NULL);
    return 0;
}

The last issue is addressed by passing a tuple with original arguments as extra argument to foo_ macro, this argument is later passed to the call operator of expression selected by _Generic.

This solution works with all major C17 compilers (gcc, clang, icc, msvc).

  • Related