3 min read

Variadic Argument Counting in C

I recently came across an interesting little C pre-processor trick to compute the number of variadic arguments using only the pre-processor.

VA_NARGS(...) and its Helpers

This is the macro definition with its supporting macros:

#define VA_NARGS(...) _VA_NARGS(_0, ##__VA_ARGS__, _VA_RSEQ63())
#define _VA_NARGS(...) _VA_ARG64(__VA_ARGS__)
#define _VA_ARG64(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \
                  _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58,  \
                  _59, _60, _61, _62, _63, _64, ...)                                                                                                           \
  _64
#define _VA_RSEQ63()                                                                                                                                          \
  62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, \
      23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0

There are some details to unpack here, but the basic idea is that VA_NARGS takes a variable number of arguments and expands to the number of arguments passed. The _VA_RSEQ63 macro provides a sequence of numbers from 62 down to 0, which is used to determine the count of arguments.

The implementation pads the variadic arguments with a dummy argument _0 at the beginning, and then uses the _VA_ARG64 macro to extract the 64th argument, which corresponds to the number of arguments passed. The ##__VA_ARGS__ syntax is used to handle the case when no arguments are passed, ensuring that the macro still works correctly.

I’ve posted the fully tested macro with tests in a GitHub gist.

Perhaps the most subtle part of this implementation is the use of the ## operator to handle cases where no variadic arguments are passed. In C99 and later, if you write VA_NARGS(), the __VA_ARGS__ will be empty, and the ## pasting operator will remove the preceding comma, allowing the macro to expand correctly. This is a quirky little trick to ensure that the macro works even with zero arguments. It works with the GNU preprocessor, but may not be portable to all compilers; that’s something to bear in mind if you plan to use this macro in a cross-platform project.

But Why?

Why would a developer want to compute the number of variadic arguments?

Good question. One reason is to implement a macro that behaves differently based on the number of variadic arguments. For example, you might want to create a logging macro that can accept a variable number of arguments and format them accordingly. By knowing the number of arguments, you can choose the appropriate formatting or handling logic. It has its uses.