August 31st, 2010 § 1 Comment
The feature of function overloading can prove to be pretty useful: it allows us to define a few versions of the same function, which differ in argument types, or even in Arity (ignoring variadic functions for the moment). Unfortunately, the C\C++ pre-processor does not allow overloading macros in the same way; It treats such attempts as redefinitions.
While we do not really need to overload a macro in order to handle different argument types (since macros ignore type information), many times it would be desired to overload a macro such that each version is able to handle a different number of arguments. This goal can actually be achieved through invocation of the VA_NUM_ARGS macro mentioned in my previous post, as we will briefly demonstrate (the idea has also been mentioned under the comment section in the aforementioned post).
Suppose we would like to create a macro which is to return the MAX of its arguments. Such feature is supported both by std::max and by the (in)famous MAX macro. The extra requirement we shall make here, other than insisting on a macro solution, is that the implementation should work for any positive number of arguments up to a certain limit, and as long as there’s at least one argument.
In this post we will provide a solution which computes the MAX of up to three arguments, but we will put an emphasis on providing a solution which is easy to scale to work with any number of arguments.
One approach, cleverly raised by a colleague of mine, suggests the following (example supporting up to three arguments):
#define MAX2(a, b) (((a)>(b)) ? (a) : (b)) #define MAX(a, ...) MAX_IMPL(a, __VA_ARGS__, a, a) #define MAX_IMPL(a, b, c, ...) MAX2(a, MAX2(b, c))
This should clearly work for the MAX macro we are after, but certainly not for every function we would like to implement; For instance, if we were after a macro which can compute the average of its arguments, this approach would clearly not work.
The generic solution for macro overloading, and specifically for our case, is by employing the VA_NUM_ARGS construct with the following set of macros:
#define macro_dispatcher(func, ...) \ macro_dispatcher_(func, VA_NUM_ARGS(__VA_ARGS__)) #define macro_dispatcher_(func, nargs) \ macro_dispatcher__(func, nargs) #define macro_dispatcher__(func, nargs) \ func ## nargs
The macro_dispatcher mechanism actually implements, like its name suggests, a kind of a dispatch mechanism (which operates through calculating the number of arguments being passed to the macro) that is actually able to simulate an overloading mechanism. Thus, enabling the following implementation for our desired max macro:
#define max(...) macro_dispatcher(max, __VA_ARGS__)(__VA_ARGS__) #define max1(a) a #define max2(a, b) ((a)>(b)?(a):(b)) #define max3(a, b, c) max2(max2(a, b), c) // ... // to verify, run the preprocessor alone (g++ -E): max(1, 2, 3);
In this scenario, the macro_dispatcher mechanism transforms the max(1, 2, 3) call to max3(1, 2, 3) invocation, which yields the desired result.
Needless to say, that the proposed technique can be utilized easily just about anywhere where macro overloading would be useful.