3

I'm writing a variadic dispatcher macro in C++, to call a different macro based on the number of arguments (from none up to 5) provided to the dispatcher. I came up with this solution:

#define GETOVERRIDE(_ignored, _1, _2, _3, _4, _5, NAME, ...) NAME
#define NAMEDARGS(...) GETOVERRIDE(ignored, ##__VA_ARGS__, NAMEDARGS5, NAMEDARGS4, NAMEDARGS3, NAMEDARGS2, NAMEDARGS1, NAMEDARGS0)(__VA_ARGS__)

NAMEDARGS is the dispatcher macro; calling it with 1 argument will result in a call to NAMEDARGS1 which takes 1 argument, and so on (I don't provide the implementations of the various NAMEDARGS# since they are irrelevant in this context).

I tested the code gcc 7.1.1, and I found a weird behavior of the gcc expansion when using the -std=c++14 flag. With this test code:

NAMEDARGS()
NAMEDARGS(int)
NAMEDARGS(int, float)

I get these expansions:

$ gcc -E testMacro.cpp
NAMEDARGS0()
NAMEDARGS1(int)
NAMEDARGS2(int, float)

$ gcc -E -std=c++14 testMacro.cpp
NAMEDARGS1()
NAMEDARGS1(int)
NAMEDARGS2(int, float)

It seems that using the -std=c++14 flag the substitution of the zero-argument call fails, resulting in the call of the one-argument macro. I thought that this could be because the ##__VA_ARGS__ syntax is a GNU extension, thus not working with an ISO C++ preprocessor; however, when trying with clang 4.0.1 I obtain the desired expansion:

$ clang -E -std=c++14 testMacro.cpp
NAMEDARGS0()
NAMEDARGS1(int)
NAMEDARGS2(int, float)

So I don't understand what's going on here. Does clang implement this gnu extension, accepting non-ISO code also with -std==c++14 unlike gcc? Or maybe the problem lies elsewhere? Thanks for the help.

Nicola Mori
  • 777
  • 1
  • 5
  • 19

1 Answers1

2

GCC defaults -std to gnu++14 (see here), which is C++14 with GNU extensions.

Comparing the two with only NAMEDARGS(...) defined shows how the expansions differ:

Code

#define NAMEDARGS(...) GETOVERRIDE(ignored, ##__VA_ARGS__, NAMEDARGS5, NAMEDARGS4, NAMEDARGS3, NAMEDARGS2, NAMEDARGS1, NAMEDARGS0)(__VA_ARGS__)
NAMEDARGS()

-std=gnu++14 -E

GETOVERRIDE(ignored, NAMEDARGS5, NAMEDARGS4, NAMEDARGS3, NAMEDARGS2, NAMEDARGS1, NAMEDARGS0)()
-------------------^

-std=c++14 -E

GETOVERRIDE(ignored,, NAMEDARGS5, NAMEDARGS4, NAMEDARGS3, NAMEDARGS2, NAMEDARGS1, NAMEDARGS0)()
-------------------^^

I'm not an experienced standard reader, but I found the following two passages in [cpp.replace] which suggest that GCC is correct in both invocations:

If the identifier-list in the macro definition does not end with an ellipsis, the number of arguments (including those arguments consisting of no preprocessing tokens) in an invocation of a function-like macro shall equal the number of parameters in the macro definition. Otherwise, there shall be more arguments in the invocation than there are parameters in the macro definition (excluding the ...). There shall exist a ) preprocessing token that terminates the invocation.

...

If there is a ... immediately preceding the ) in the function-like macro definition, then the trailing arguments, including any separating comma preprocessing tokens, are merged to form a single item: the variable arguments. The number of arguments so combined is such that, following merger, the number of arguments is one more than the number of parameters in the macro definition (excluding the ...).

It seems correct then that an empty __VA_ARGS__ is expanded to a single empty argument.

I can't find whether clang's behaviour here is intended.

Mark
  • 1,016
  • 6
  • 10
  • Thanks Mark, this is exactly what I thought, and the reason why I decided to try with a (supposed) ISO C++ preprocessor like clang (at least AFAIK). I expected a similar behavior to gcc -std=c++14 and got surprised when seeing the result. – Nicola Mori Aug 02 '17 at 10:18