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.