2

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?

4
  • ##__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 Commented Oct 20, 2022 at 8:46
  • 1
    Also read this learn.microsoft.com/en-us/cpp/preprocessor/…. They are talking about C++ there. I have no idea if or how this applies to C. Commented Oct 20, 2022 at 8:52
  • I recently posted a pretty universal solution here: stackoverflow.com/a/73934150/584518 Commented Oct 20, 2022 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. Commented Oct 20, 2022 at 19:09

1 Answer 1

4

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

Sign up to request clarification or add additional context in comments.

5 Comments

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.
@tilz0R, it is gcc extension. It will raise a warning when compiled in pedantic mode. See godbolt.org/z/z7czvGvbc
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?
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, 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.