2

I'm trying to understand the argument counting in C preprocessing macro and the idea in this answer. We have the following macro (I changed the number of arguments for simplicity):

#define HAS_ARGS(...) HAS_ARGS_(__VA_ARGS__, 1, 1, 0,)
#define HAS_ARGS_(a, b, c, N, ...) N

As far as I understand the purpose of this macro is to check if the given varargs empty. So on empty varargs the macro invokation is replaced with 0 which seems fine. But with a single argument it also turns into 0 which I seems strange.

HAS_ARGS(); //0
HAS_ARGS(123); //also 0
HAS_ARGS(1, 2); //1

LIVE DEMO

I think I understand the reason. In case of empty varargs a is replaced with empty preprocessing token, in case of a single argument vararg a is replaced with the argument yielding the same result.

Is there a way to get 0 returned in case varargs are empty, 1 in case argument number is from 1 to the defined in HAS_ARGS_ macro invokation without using comma-swallowing or other non-conforming tricks. I mean

SOME_MACRO_F() //0
SOME_MACRO_F(234) //1
SOME_MACRO_F(123, 132) //1
//etc
Lundin
  • 195,001
  • 40
  • 254
  • 396
Some Name
  • 8,555
  • 5
  • 27
  • 77

2 Answers2

5

You cannot pass zero arguments to HAS_ARGS(...). ISO C (and C++, at least for the next two years) requires that an ellipsis corresponds to at least one additional argument after the last named one.

If there are no named ones, then the macro needs to be passed at least one argument. In the case of HAS_ARGS() the extra argument is simply an empty token sequence. Zero arguments is simply not possible.

This is exactly the use case in the answer. The target macro expects at least one argument. So we can use a wrapper accepting only an ellipsis for "overload resolution". A better name probably would have been HAS_MORE_THAN_1_ARGS. Because that's what the predicate is meant to tell you. Alas, I favored brevity on that answer.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • _ISO C (and C++, at least for the next two years) requires that an ellipsis corresponds to at least one additional argument after the last named one_ It looked at N1570 and could find this `6.9.1(p8)`: `If a function that accepts a variable number of arguments is defined without a parameter type list that ends with the ellipsis notation, the behavior is undefined.` But I'm not sure we can apply it here, because this is for regular functions, not macro functions. Can you please elaborate? – Some Name Jan 14 '19 at 08:25
  • @SomeName - The wording for *functions* doesn't apply to *macros*. You want to be looking at [6.10.3p4](https://port70.net/~nsz/c/c11/n1570.html#6.10.3p4). – StoryTeller - Unslander Monica Jan 14 '19 at 08:27
  • Also, please don't think macros are anything like functions. Many a programmer fell down that pothole in the road. They look like function calls (hence the "function-like" verbiage), but that's where the similarity ends. – StoryTeller - Unslander Monica Jan 14 '19 at 08:30
  • For macro we can provide at least one argument in place of ellipsis: `there shall be more arguments in the invocation than there are parameters in the macro definition (excluding the ...).` But if a vararg function has non-empty parameter type list we can provide no arguments in place of ellipsis. Is it correct? – Some Name Jan 14 '19 at 08:33
  • 1
    @SomeName - Yeah, that is correct. A major difference is that a vararg function needs at least one named argument (to use `va_list`). A macro can defined to accept only ellipsis. A function operates on run-time entities, a macro on tokens. – StoryTeller - Unslander Monica Jan 14 '19 at 08:37
  • In the last revision ISO/IEC 9899:2017 **C17**, **§6.10.3 Macro replacement**, sub 10, appears the definition: `# define identifier lparen ... ) replacement-list new-line`, which **explicitly** refer to a function like macro definition having only elipsis as parameter. So it is actually considered of **perfectly legal and compliant use**. – Frankie_C Jan 14 '19 at 10:22
  • @Frankie_C - I spoke of arguments, not parameters. The whole premise of this approach is that `HAS_ARG(...)` is a valid macro definition, but cannot be called with **zero arguments** like `HAS_ARG()`. 6.10.3p4 still applies, even if it may have moved. – StoryTeller - Unslander Monica Jan 14 '19 at 10:28
  • It is absolutely clear that the method couldn't work without **at least one parameter**. And in the past it has always been considered that function like macros should have at least one parameter. But as per C17 standard, has been clarified that it is legal. So maybe you would correct the affirmation in your answer that the parameter is required by the standard. – Frankie_C Jan 14 '19 at 10:35
  • @Frankie_C - There was no need for a *parameter* since at least c99. There is however a need for at least one **argument**! – StoryTeller - Unslander Monica Jan 14 '19 at 10:36
  • Since `HAS_ARGS()` yields UB is it possible to emit some warning in such case? `-pedantic` does not seem to work. Or non-conforming preprocessor usage cannot be caught with some warnings or sort of this? – Some Name Jan 15 '19 at 06:50
  • @SomeName - You mean when there are more than the max arguments or zero? I edited a bit to clarify since yesterday. `HAS_ARG()` actually passes one argument, an empty token sequence. It's unintuitive but not UB. – StoryTeller - Unslander Monica Jan 15 '19 at 06:59
  • But in case if we have macro function `#define PRINT(a, ...) printf(a, ##__VA_ARGS__)` it would be UB to call it as `PRINT("test");`, but seems ok to call it as `PRINT("test",);`. Would be possible to catch such UB? – Some Name Jan 15 '19 at 07:15
  • @SomeName - I think Clang and GCC should warn about it with `-Wextra`. Not sure however. – StoryTeller - Unslander Monica Jan 15 '19 at 07:19
2

It seems difficult to compute that at compile-time, but you can do it at run-time by stringifying the arguments and testing if the string is empty.

Tested with gcc:

#include <stdio.h>
#define HAS_ARGS(...) (#__VA_ARGS__[0] != '\0')

int main()
{
   printf("%d %d %d %d\n",HAS_ARGS(),HAS_ARGS(10),HAS_ARGS(20,"foo"),HAS_ARGS(10,20));
    return 0;
}

this prints:

0 1 1 1

behind the scenes, here's what the pre-processor outputs:

int main()
{
   printf("%d %d %d %d\n",(("")[0] != '\0'),(("10")[0] != '\0'),(("20,\"foo\"")[
0] != '\0'),(("10,20")[0] != '\0'));
    return 0;
}
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219