Search code examples
cmacrosc11

Can C11 _Generic be used with no arguments?


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?


Solution

  • 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 &"".