Search code examples
coverloading

Function overloading in C with _Generic when __VA_ARG__ can be empty


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?


Solution

  • 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).