Macro to count number of arguments

Another possibility, which does not use sizeof nor a GCC extension is to add the following to your code

#define PP_COMMASEQ_N()                                    \
          1,  1,  1,  1,                                   \
          1,  1,  1,  1,  1,  1,  1,  1,  1,  1,           \
          1,  1,  1,  1,  1,  1,  1,  1,  1,  1,           \
          1,  1,  1,  1,  1,  1,  1,  1,  1,  1,           \
          1,  1,  1,  1,  1,  1,  1,  1,  1,  1,           \
          1,  1,  1,  1,  1,  1,  1,  1,  1,  1,           \
          1,  1,  1,  1,  1,  1,  1,  1,  0,  0

#define PP_COMMA(...)    ,

#define PP_HASCOMMA(...)                                   \
          PP_NARG_(__VA_ARGS__, PP_COMMASEQ_N())

#define PP_NARG(...)                                       \
          PP_NARG_HELPER1(                                 \
              PP_HASCOMMA(__VA_ARGS__),                    \
              PP_HASCOMMA(PP_COMMA __VA_ARGS__ ()),        \
              PP_NARG_(__VA_ARGS__, PP_RSEQ_N()))

#define PP_NARG_HELPER1(a, b, N)    PP_NARG_HELPER2(a, b, N)
#define PP_NARG_HELPER2(a, b, N)    PP_NARG_HELPER3_ ## a ## b(N)
#define PP_NARG_HELPER3_01(N)    0
#define PP_NARG_HELPER3_00(N)    1
#define PP_NARG_HELPER3_11(N)    N

The result is

PP_NARG()       // expands to 0
PP_NARG(x)      // expands to 1
PP_NARG(x, 2)   // expands to 2

Explanation:

The trick in these macros is that PP_HASCOMMA(...) expands to 0 when called with zero or one argument and to 1 when called with at least two arguments. To distinguish between these two cases, I used PP_COMMA __VA_ARGS__ (), which returns a comma when __VA_ARGS__ is empty and returns nothing when __VA_ARGS__ is non-empty.

Now there are three possible cases:

  1. __VA_ARGS__ is empty: PP_HASCOMMA(__VA_ARGS__) returns 0 and PP_HASCOMMA(PP_COMMA __VA_ARGS__ ()) returns 1.

  2. __VA_ARGS__ contains one argument: PP_HASCOMMA(__VA_ARGS__) returns 0 and PP_HASCOMMA(PP_COMMA __VA_ARGS__ ()) returns 0.

  3. __VA_ARGS__ contains two or more arguments: PP_HASCOMMA(__VA_ARGS__) returns 1 and PP_HASCOMMA(PP_COMMA __VA_ARGS__ ()) returns 1.

The PP_NARG_HELPERx macros are just needed to resolve these cases.

Edit:

In order to fix the func(0, ) problem, we need to test whether we have supplied zero
or more arguments. The PP_ISZERO macro comes into play here.

#define PP_ISZERO(x)    PP_HASCOMMA(PP_ISZERO_HELPER_ ## x)
#define PP_ISZERO_HELPER_0    ,

Now let’s define another macro which prepends the number of arguments to an argument list:

#define PP_PREPEND_NARG(...)                               \
          PP_PREPEND_NARG_HELPER1(PP_NARG(__VA_ARGS__), __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER1(N, ...)                    \
          PP_PREPEND_NARG_HELPER2(PP_ISZERO(N), N, __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER2(z, N, ...)                 \
          PP_PREPEND_NARG_HELPER3(z, N, __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER3(z, N, ...)                 \
          PP_PREPEND_NARG_HELPER4_ ## z (N, __VA_ARGS__)
#define PP_PREPEND_NARG_HELPER4_1(N, ...)  0
#define PP_PREPEND_NARG_HELPER4_0(N, ...)  N, __VA_ARGS__

The many helpers are again needed to expand the macros to numeric values. Finally test it:

#define my_func(...)  func(PP_PREPEND_NARG(__VA_ARGS__))

my_func()          // expands to func(0)
my_func(x)         // expands to func(1, x)
my_func(x, y)      // expands to func(2, x, y)
my_func(x, y, z)   // expands to func(3, x, y, z)

Online example:

http://coliru.stacked-crooked.com/a/73b4b6d75d45a1c8

See also:

Please have also a look at the P99 project, which has much more
advanced preprocessor solutions, like these.

Leave a Comment

tech