19

I know I can do this:

#define MACRO(api, ...) \
  bool ret = api(123, ##__VA_ARGS__);

This is just an example, it's part of a more complicated solution. The point is that I need to append the variable number of arguments to the first 123. The ## makes the compiler strip out the comma after the 123 argument if no arguments were passed into MACRO.

But now I want to append arguments to api, like so:

#define MACRO(api, ...) \
  bool ret = api(__VA_ARGS__##, 456);

Nocando. One solution is to have two macros, MACRO and MACRO_V, say, and make the _V version not process any arguments. But is there a way to make it work with one macro?

a3f
  • 8,517
  • 1
  • 41
  • 46
Mike
  • 2,393
  • 3
  • 25
  • 37

3 Answers3

23

Yes, you can. The following supports up to 4 arguments, but it can be trivially expanded to support more:

#define MACRO(api, ...) \
    bool ret = api(__VA_ARGS__ VA_COMMA(__VA_ARGS__) 456)

/*
 * VA_COMMA() expands to nothing if given no arguments and a comma if
 * given 1 to 4 arguments.  Bad things happen if given more than 4
 * arguments.  Don't do it.
 */
#define VA_COMMA(...) GET_6TH_ARG(,##__VA_ARGS__,COMMA,COMMA,COMMA,COMMA,)
#define GET_6TH_ARG(a1,a2,a3,a4,a5,a6,...) a6
#define COMMA ,

/* EXAMPLES */
MACRO(foo)                       /* bool ret = foo( 456)              */
MACRO(foo,1)                     /* bool ret = foo(1 , 456)           */
MACRO(foo,1,2,3,4)               /* bool ret = foo(1,2,3,4 , 456)     */
/* uh oh, too many arguments: */
MACRO(foo,1,2,3,4,5)             /* bool ret = foo(1,2,3,4,5 5 456)   */
MACRO(foo,1,2,3,4,5,6)           /* bool ret = foo(1,2,3,4,5,6 5 456) */

This same trick is used to:

Explanation

VA_COMMA surrounds its arguments (__VA_ARGS__) with six additional arguments: one empty argument before (doesn't have to be empty—it's thrown away) and four commas and an empty argument after.

These six or more arguments are passed to GET_6TH_ARG, which, as its name implies, expands to the sixth argument. All other arguments are discarded.

Thus, MACRO(foo) is expanded like this:

step 0: MACRO(foo)
step 1: bool ret = foo( VA_COMMA() 456)
step 2: bool ret = foo( GET_6TH_ARG(,COMMA,COMMA,COMMA,COMMA,) 456)
step 3: bool ret = foo( 456)

MACRO(foo,1) is expanded like this:

step 0: MACRO(foo,1)
step 1: bool ret = foo(1 VA_COMMA(1) 456)
step 2: bool ret = foo(1 GET_6TH_ARG(,1,COMMA,COMMA,COMMA,COMMA,) 456)
step 3: bool ret = foo(1 COMMA 456)
step 4: bool ret = foo(1 , 456)

MACRO(foo,1,2) is expanded like this:

step 0: MACRO(foo,1,2)
step 1: bool ret = foo(1,2 VA_COMMA(1,2) 456)
step 2: bool ret = foo(1,2 GET_6TH_ARG(,1,2,COMMA,COMMA,COMMA,COMMA,) 456)
step 3: bool ret = foo(1,2 COMMA 456)
step 4: bool ret = foo(1,2 , 456)

MACRO(foo,1,2,3,4,5) is expanded like this:

step 0: MACRO(foo,1,2,3,4,5)
step 1: bool ret = foo(1,2,3,4,5 VA_COMMA(1,2,3,4,5) 456)
step 2: bool ret = foo(1,2,3,4,5 GET_6TH_ARG(,1,2,3,4,5,COMMA,COMMA,COMMA,COMMA,) 456)
step 3: bool ret = foo(1,2,3,4,5 5 456)
Community
  • 1
  • 1
Richard Hansen
  • 51,690
  • 20
  • 90
  • 97
  • Is it possible that this answer is out-of-date with C++11 and newer? I've made a simple demo which compiles with gcc 7.5 with no flags, but fails when specifying -std=c++11, with --- error: expected primary-expression before ',' token #define COMMA , --- With -pedantic errors, gcc says that "ISO C++11 requires at least one argument for the "..." in a variadic macro" – jwimberley Sep 01 '21 at 17:50
  • 2
    @jwimberley A solution that strictly conforms to the standard is much more complicated (see https://stackoverflow.com/a/11172679 for inspiration), so you have to decide whether you want your code to be standards conformant or readable. The original question implied that it was OK to use the non-standard `##__VA_ARGS__` construct, so I used it in my answer. – Richard Hansen Sep 01 '21 at 20:24
4

No. The behaviour of ## which allows this to work in the first case is a GCC extension (C99 does not allow the variable argument part to be empty), and it specifically applies to the case with a comma on the left and __VA_ARGS__ on the right. See e.g. http://gcc.gnu.org/onlinedocs/gcc-4.5.1/gcc/Variadic-Macros.html#Variadic-Macros (at the bottom of the page).

Matthew Slattery
  • 45,290
  • 8
  • 103
  • 119
3

Well, I think it is possible with something like this:

#define NO_FIRST(first, ...) __VA_ARGS__
#define DO_APPEND_LAST(last, ...) NO_FIRST(__VA_ARGS__, last)
#define MACRO(api, ...) bool ret = api(DO_APPEND_LAST(456, dummy, ##__VA_ARGS__));

Haven't check it, however should work in latest VS and gcc.

Alex
  • 63
  • 1
  • 6