1

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?

  • ##__VA_ARGS__ is a compiler extension by gcc. It removes the preceding comma if \_\_VA_ARGS\_\_ is empty. Visual Studio may or may not support it – n. m. could be an AI Oct 20 '22 at 08:46
  • 1
    Also read this https://learn.microsoft.com/en-us/cpp/preprocessor/preprocessor-experimental-overview?view=msvc-170. They are talking about C++ there. I have no idea if or how this applies to C. – n. m. could be an AI Oct 20 '22 at 08:52
  • I recently posted a pretty universal solution here: https://stackoverflow.com/a/73934150/584518 – Lundin Oct 20 '22 at 10:56
  • @Lundin I do like your solution. However, the answer produces the result with less complexity. Also, my question does pertain to achieving function overloading using _Generic. So, I decided to select it. – solaremperor Oct 20 '22 at 19:09

1 Answers1

1

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

tstanisl
  • 13,520
  • 2
  • 25
  • 40
  • Not sure this is true: `Other is #define foo(_X, ...) which will not work for foo(1) because the function macro expect two or more arguments.` – unalignedmemoryaccess Oct 20 '22 at 10:34
  • @tilz0R, it is gcc extension. It will raise a warning when compiled in pedantic mode. See https://godbolt.org/z/z7czvGvbc – tstanisl Oct 20 '22 at 10:37
  • Can someone unpack this for me: `#define foo(...) foo_((__VA_ARGS__), __VA_ARGS__, NO_ARG, ~)` Why pass `__VA_ARGS__` twice, once within the parentheses and once outside? – solaremperor Oct 25 '22 at 00:56
  • I ask because when I use this structure in VS 2019 `#define foo_(args, a, b, c, ...) _Generic((c), NoArg: foo_two, default: foo_three) args` Both `foo_two` and `foo_three` (assuming similar but appropriate code as the example main function) have linting which says _expected an expression_. However, it compiles and runs as expected. – solaremperor Oct 25 '22 at 01:35
  • 1
    @solaremperor, I've compiled it with `/std:c11` option from MSVC 19.33. The problem was reconstructing original parameters from `a,b,...` because `b` and `...` may not exist. It was simpler to keep a copy of original argument list. Don't worry. The list may be expanded twice, but the expressions on it are executed only once. – tstanisl Oct 25 '22 at 09:30