1

I am trying to write a C variadic macro that checks if it was invoked with one, or many arguments.

I found some solutions which use a macro that counts the arguments, but those solutions are either for a fixed/finite amount of arguments, or rely on the arguments sharing the same type:

#define VA_NARGS_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N
#define VA_NARGS(...) VA_NARGS_IMPL(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

Counts arguments upto 10, then fails. However I think that checking 1 vs many arguments is an easier problem, so it might have a solution that will work with any type and for any number of arguments.

My use for this is in a sort of debug_log macro:

#define debug_log(...) \
    do { \
        fprintf(verbose, " - %lu ", time(NULL)); \
        if (VA_HAS_MANY_ARGS(__VA_ARGS__)) { \
            fprintf(verbose, __VA_ARGS__); \
        } else {
            fprintf(verbose, "%s\n", __VA_ARGS__); \
        } \
    while (0)

Which can be invoked as debug_log("string here"); but also debug_log("string there %i\n", 5); and will produce:

- current_time_here string here\n
- current_time_there string there 5\n

respectively.

J08nY
  • 333
  • 3
  • 8
  • There might be a much better solution to the problem leading to *this* detail *without* using variadic macros at all ... –  May 23 '17 at 16:32
  • Looks like an XCY-problem. What is your **actual** problem you try to solve? Don't get too fancy with macros! – too honest for this site May 23 '17 at 16:35
  • @Olaf Well I have a debug macro, something like: `#define debug_log(...) do { fprintf(verbose, " - %lu %s\n", time(NULL), __VA_ARGS__); } while(0)` Which works fine, however lacks the variadic nature. What I want is a macro that I can invoke with `debug_log("whatever string");` but also `debug_log("whatever format string %i\n", 5);` and it will produce: " - current_time_here whatever string\n", " - current_time_here whatever format string 5\n" respectively. – J08nY May 23 '17 at 16:43
  • Sounds awful and error-prone. What keeps you from using two different macros? – too honest for this site May 23 '17 at 21:47
  • Well nothing really, only that I wanted a solution that just works nice, I have found it, although not via counting variadic arguments, but just by using them better. Pretty much just doing 3 fprintfs in the macro fixed it. The first one prefixes the time, the second gets passed the __VA_ARGS__ and the third one prints the newline. Thus allows for the behaviour I wanted, but still doesn't solve the original question so thats left unsolved. – J08nY May 23 '17 at 22:24
  • Also why / how would that be error prone, or awful? I understands macros are not save all, but this is a pretty nice use for them. I need debug prints in my debug builds, with timing and format strings. – J08nY May 23 '17 at 22:25
  • 1
    "However I think that checking 1 vs many arguments is an easier problem" ...curious... if you know how to do A, but you don't know how to do B, then on what basis do you surmise that B is easier than A? – H Walters May 24 '17 at 02:42

1 Answers1

2

Here's how you might code a one-or-many macro:

#define SECOND(...) SECOND_I(__VA_ARGS__,,)
#define SECOND_I(A,B,...) B
#define SHIFT_IN_ZERO , 0
#define ONE_OR_MANY_UTILITY(...) ONE_OR_MANY_UTILITY_I(__VA_ARGS__, SHIFT_IN_ZERO, X)
#define ONE_OR_MANY_UTILITY_I(A,B,...) B
#define ONE_OR_MANY(...) SECOND(ONE_OR_MANY_UTILITY(__VA_ARGS__), 1)

However, note that this is a fairly complex macro system (more complex than VA_NARGS), and I'm not sure you're gaining from this in the use case in your question (which I consider part of the question).

What you are in essence trying to accomplish seems misguided to me. You want to collapse various usages down to a single macro, debug_log; this itself is a type of polymorphism. That's a gain. However, you're specifically trying to make one of these cases (calling debug_log with one parameter) behave differently (add a NL for you) from the other cases (calling debug_log with more than one parameter)... and this negates any benefit you gain.

If you spell the macro the same way, it should be doing the same thing. Benefiting me by allowing me to only have to remember/use(/transform) one macro, but then forcing me to learn(/change) how to use it based on how many arguments I pass it, is just teasing me. Either debug_log should always add a NL for me, or it should never add one.

It would be better to use two different macros, since these do two different things. However, it would be even better to collapse these macros down into a single macro that always does the same kind of thing.

In this case, forcing the NL in is probably a good idea; that helps ensure that your log file itself is usable. But instead of adding the NL when you call it with one parameter, but not when called with multiple parameters, why not just always add the NL?:

#define debug_log(...) \
    do { \
         /* fprintf metadata--timestamp, file, log level, etc here */ \
         fprintf(verbose, __VA_ARGS__); \
         fputs("\n", verbose); \
        } \
    while (0)
H Walters
  • 2,634
  • 1
  • 11
  • 13
  • Thanks! I ended up doing exactly the `debug_log` macro you specified, but as it didn't really solve the question I didn't post it. – J08nY May 24 '17 at 12:51