Using a simplified version what Jens spelled out, a possible approach is described below.
E_ is used to inject an argument, while I_ is used to cause indirect invocation of a macro. V_ is required to add an additional level of indirection, which seems required for some compilers.
#define V_(...) __VA_ARGS__
#define I_(M,...) V_(M(__VA_ARGS__))
#define E_() _,
Now, suppose you need a macro MAC to handle 0, 1, or 2 arguments:
MAC(1, 2);
MAC(1);
MAC();
One possible implementation might look like:
#define MAC_X(_2,_1,X,...) MAC_##X
#define MAC(...) V_(V_(MAC_X(__VA_ARGS__,2,_))(__VA_ARGS__))
#define MAC_2(...) function_for_2(__VA_ARGS__)
#define MAC_1(...) function_for_1(__VA_ARGS__)
#define MAC_0(...) function_for_0(__VA_ARGS__)
#define MAC_Y(_1,Y,...) MAC_##Y
#define MAC__(...) I_(MAC_Y, E_ __VA_ARGS__ () 0, 1)(__VA_ARGS__)
And expanding it to 3 arguments is relatively straightforward. The only messiness is detecting 1 or 0 arguments, which does not change:
-#define MAC_X(_2,_1,X,...) MAC_##X
-#define MAC(...) V_(V_(MAC_X(__VA_ARGS__,2,_))(__VA_ARGS__))
+#define MAC_X(_3,_2,_1,X,...) MAC_##X
+#define MAC(...) V_(V_(MAC_X(__VA_ARGS__,3,2,_))(__VA_ARGS__))
+#define MAC_3(...) function_for_3(__VA_ARGS__)
#define MAC_2(...) function_for_2(__VA_ARGS__)
For more than 1 argument, the MAC macro expands into MAC_2 (or MAC_3). But, for 1 or 0 arguments, the MAC macro expands to MAC__.
The MAC__ macro applies Jens' trick to detect whether it was passed 1 or 0 arguments. It does this with the E_ helper macro, and injecting its argument between E_ and the () that would cause it to be invoked. If there are 0 arguments, the E_ is invoked, and an argument is injected. The injected argument causes 0 to be the second argument for MAC_Y to select. If there is 1 argument, E_ is not expanded. The first argument to the to MAC_Y becomes E_ ... () 0 (where the ... is whatever the first argument was), and the second argument for MAC_Y is 1. This allows MAC_Y to invoke either MAC_0 or MAC_1.