3

I would like to use _Generic to overload functions, one of them having no arguments, something like:

#include <stdio.h>
#include <string.h>

void f1(void)
{
    printf("F1\n");
}

void f2(int n)
{
    printf("F2 %d\n", n);
}

#define func(x) _Generic((x),   \
    int: f2,                    \
    default: f1                 \
    )(x)

int main(void)
{
    func(); // I want this to call f1
    return 0;
}

Is this possible?

Dan
  • 2,452
  • 20
  • 45
  • 2
    I do not see any way of doing it in C. I would suggest moving to C++ – 0___________ Mar 15 '23 at 19:35
  • 6
    [You have asked the wrong question.](https://en.wikipedia.org/wiki/XY_problem) Your goal is to create a macro that will do one thing or another depending on whether it is passed an argument or not, and possibly on what type its argument is, and you thought about maybe solving it with `_Generic`, and so you asked about your speculative solution, `_Generic`, instead of your actual problem, implementing a macro. As for `_Generic`, the C grammar is clear; the standard `_Generic` operator must have an operand. (Specifically, its first operand must be an *assignment-expression*.) – Eric Postpischil Mar 15 '23 at 19:41
  • 1
    You need `__VA_OPT__`. It will be included in C23 and most compilers already support it. – tstanisl Mar 15 '23 at 20:20
  • I'm not a fan of C11's `_Generic`. If it's anything beyond trivial, it's [IMO] a poor substitute for C++'s templates or virtual member functions. Here's an answer of mine: (Writing a 'generic' struct-print method in C)(https://stackoverflow.com/a/65621483/5382650) that does a bit of what C++ does in a C implementation that may help a bit. – Craig Estey Mar 15 '23 at 20:25
  • 1
    @EricPostpischil — I got the roles of `f1` and `f2` reversed. Sorry. Comment removed. – Jonathan Leffler Mar 15 '23 at 20:36
  • 1
    @EricPostpischil [comment](https://stackoverflow.com/questions/75749235/can-c11-generic-be-used-with-no-arguments#comment133626575_75749235) makes for a good _generic_ (pun intended) template to use for many an XY question. – chux - Reinstate Monica Mar 15 '23 at 21:55
  • Related: it is possible to mimic C++ default arguments, including empty argument lists, like this: https://stackoverflow.com/a/73934150/584518. But that's one single function called in multiple different ways, so it's a slightly different scenario. – Lundin Mar 16 '23 at 14:40

3 Answers3

3

Due to a fundamental issue in C pre-processor, elegant dispatching between function-like macro with a single and no-parameter is not possible without extensions. The upcoming C23 standard will support __VA_OPT__ which injects an extra token when the __VA_ARGS__ is non-empty. Usually it is a extra comma. This feature is already supported by some compilers including GCC and CLANG.

You can use NoArg trick as in the other answer but enhance it with __VA_OPT__ to support func() nicely.

#include <stdio.h>
#include <string.h>

void f1(void) { puts("F1");}
void f2(int n) { printf("F2 %d\n", n); }

typedef struct NoArg NoArg;

#define func(...) func_(__VA_ARGS__ __VA_OPT__(,) (NoArg*)0, ~ )(__VA_ARGS__)

#define func_(x, ...) \
  _Generic((x)        \
    , NoArg*: f1      \
    , int: f2         \
  )

int main(void) {
    func();
    func(5);
    return 0;
}

Works as expected. See godbolt.

The tricky part is the expansion of func macro to:

func_(__VA_ARGS__ __VA_OPT__(,) (NoArg*)0, ~ ) (__VA_ARGS__)

The tail of (__VA_ARGS__) keeps the original arguments to be used after the func_ is expanded to _Generic. The __VA_OPT__(,) adds a comma if and only if the parameter list is empty. Next, the (NoArg*)0 indicator is passed. It will be the first parameter is original parameter list was empty. Finally, the parameters are finished with dummy ~ to make sure that func_ macro is evaluated with at least two tokens letting use func_(x, ...) without issues with missing arguments.

EDIT

There is a way to do it in C11. Just use compound literal of an array type.

Use (int[]) { 0, __VA_ARGS__ }. Let empty list in func() expand to (int[]) { 0, } which is an array of type int[1] while let 5 in func(5) expands to (int[]) { 0, 5 } which is an array of type int[2]. Next take an address of such array to avoid "array decay" in _Generic. Dispatch a proper function from the type of an array pointer.

#include <stdio.h>
#include <string.h>

void f1(void) { puts("F1");}
void f2(int n) { printf("F2 %d\n", n); }

#define func(...) func_((&(int[]){ 0, __VA_ARGS__})) (__VA_ARGS__)
#define func_(x)    \
  _Generic((x)      \
    , int(*)[1]: f1 \
    , int(*)[2]: f2 \
  )

int main(void) {
    func(); // This will call f1
    func(5);     // This will call f2 with argument of 5
    return 0;
}

It compiles in a pedantic mode with no warning and it produces the expected output. See godbolt. The main disadvantage it that it can work only for integer parameters.

EDIT

As pointed by a comment from Lundin, there is even a simpler version that works for functions with any type.

#define func(...) \
  _Generic (& # __VA_ARGS__  \
    , char(*)[1]: f1         \
    , default: f2            \
  )(__VA_ARGS__)

The trick is stringification of parameter list. The empty one will be transformed to "". The extra spaces are removed as pointed in 6.10.3.2p2:

... White space before the first preprocessing token and after the last preprocessing token composing the argument is deleted. ...

The interesting thing about string literal is that they are technically L-values so one can take their address! It allows to dispatch expression from the type of the array pointer which is char(*)[1] for &"".

tstanisl
  • 13,520
  • 2
  • 25
  • 40
  • Nice answer, unfortunately the argument is evaluated twice. – David Ranieri Mar 15 '23 at 23:18
  • 1
    @DavidRanieri, it is "expanded" twice, but "evaluated" only once. The first argument of `_Generic` is never evaluated. – tstanisl Mar 15 '23 at 23:23
  • oops you are right, I forgot about that – David Ranieri Mar 15 '23 at 23:28
  • 1
    In order to execute f1 in case of zero args or otherwise f2, you could do `&(char[]){ #__VA_ARGS__})` ... `char(*)[1]: f1, default: f2`. This works with any type not just int, but only to separate the cases of no args vs args. – Lundin Mar 16 '23 at 14:35
  • @Lundin, I was considering that but I was afraid that some compilers may preserve white-spaces i.e. in `func( )`. After some reading, it looks that the must be removed ([6.10.3.2p2](https://port70.net/~nsz/c/c11/n1570.html#6.10.3.2p2)). Thanks. – tstanisl Mar 16 '23 at 15:51
  • That particular trick was discussed here: https://stackoverflow.com/q/55417186/584518 – Lundin Mar 16 '23 at 16:01
  • @Lundin, Actually, it could fail if a macro expanding to an empty list was used as an argument of `func()`. See [godbolt](https://godbolt.org/z/feK1a6jrc). However, it can be easily fixed by two step expansion. – tstanisl Mar 16 '23 at 16:05
  • @Lundin, I came up with a small improvement over proposed fix. – tstanisl Mar 16 '23 at 16:19
  • @tstanisl Yeah well that's the same well-known issue as "stringification" macros so it probably won't come as a surprise. – Lundin Mar 17 '23 at 07:28
1

Cool thing about C is that it is pretty bare bones, so you can come up with some bare bones solutions like:

#include <stdio.h>
#include <string.h>

void f1(void *_)
{
    printf("F1\n");
}

void f2(int n)
{
    printf("F2 %d\n", n);
}

typedef struct { char _; } noarg_t;

#define NOARG &((noarg_t){0})

#define func(x) _Generic((x),       \
    int: f2,                        \
    noarg_t*: f1                    \
    )((void *)(x))

int main(void)
{
    func(NOARG); // This will call f1
    func(5);     // This will call f2 with argument of 5
    return 0;
}

It is not part of any standard and doesn't look as pretty as actually using no arguments, but it reads the same at least.

I hope this helps.

Miguel Aragon
  • 148
  • 1
  • 9
1

Not using _Generic but with a little help of the preprocessor:

#include <stdio.h>
#include <string.h>

void f1(void)
{
    printf("F1\n");
}

void f2(int n)
{
    printf("F2 %d\n", n);
}

#define func(...)                                   \
    (sizeof((int []){0, __VA_ARGS__}) > sizeof(int) \
    ? f2(__VA_ARGS__ +0)                            \
    : f1())

int main(void)
{
    func();
    func(42);
    return 0;
}

Output:

F1
F2 42

This part:

func();
func(42);

expands to:

(sizeof((int []){0, }) > sizeof(int) ? f2( +0) : f1());
(sizeof((int []){0, 42}) > sizeof(int) ? f2(42 +0) : f1());

There is no danger of the argument being evaluated twice since sizeof does not evaluate the argument, it only computes the size of the passed type, I agree ... is horrible.

David Ranieri
  • 39,972
  • 7
  • 52
  • 94