PreProcessor Overloading

PreProcessor Overloading

While it is simple to overload methods in C++, the preprocessor doesn’t work the same way. To define overloaded methods in the preprocessor, we need to define a method for each number of arguments we want to overload.

Option 1:
// A C++ Method
void ProcessMessage(a = 0, b=0, c=0) { /* impl… */ }

// Alternatively using the Preprocessor:
#define MSG_0() /* impl… */
#define MSG_1(a) /* impl… */
#define MSG_2(a, b) /* impl… */
#define MSG_3(a, b, c) /* impl… */
#define GET_MSG_ARGS(a, b, c, name, ...) name
#define MSG_MACRO_CHOOSER(...) \
   GET_MSG_ARGS(__VA_ARGS__, MSG_3, MSG_2, MSG_1, MSG_0)
#define LMSG(...) MSG_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)

However, this method is not the most portable, and sometimes doesn’t work with MSVC, so a better approach is using the solution from rextester ONP80107.

Option 2:
#include <iostream>

// General utility macro
#define PP_CAT( A, B ) A ## B
#define PP_EXPAND(...) __VA_ARGS__

// Macro overloading feature support
#define PP_VA_ARG_SIZE(...) PP_EXPAND(PP_APPLY_ARG_N((PP_ZERO_ARGS_DETECT(__VA_ARGS__), PP_RSEQ_N)))

#define PP_ZERO_ARGS_DETECT(...) PP_EXPAND(PP_ZERO_ARGS_DETECT_PREFIX_ ## __VA_ARGS__ ## _ZERO_ARGS_DETECT_SUFFIX)
#define PP_ZERO_ARGS_DETECT_PREFIX__ZERO_ARGS_DETECT_SUFFIX ,,,,,,,,,,,0

#define PP_APPLY_ARG_N(ARGS) PP_EXPAND(PP_ARG_N ARGS)
#define PP_ARG_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N,...) N
#define PP_RSEQ_N 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0

#define PP_OVERLOAD_SELECT(NAME, NUM) PP_CAT( NAME ## _, NUM)
#define PP_MACRO_OVERLOAD(NAME, ...) PP_OVERLOAD_SELECT(NAME, PP_VA_ARG_SIZE(__VA_ARGS__))(__VA_ARGS__)

// Usage example
#define FOO(...)       PP_MACRO_OVERLOAD(FOO, __VA_ARGS__)
#define FOO_0()        "Zero"
#define FOO_1(X)       "One"
#define FOO_2(X, Y)    "Two"
#define FOO_3(X, Y, Z) "Three"

int main()
{
    std::cout << FOO() << "\n" << 
                 FOO(1) << "\n" << 
                 FOO(1, 2) << "\n" << 
                 FOO(1, 2, 3) << std::endl;
}

However, this solution doesn’t work out of the box on Windows, as MSVC expands tokens a bit differently than posix platforms. To correctly do the symbol expansion we need to an extra call to get the preprocessor to give us the output we expect. Yes, this looks like it shouldn’t do anything, but it causes the preprocessor to evaluate the symbol a touch differently where it passes values instead of blank symbols definitions.

// MSVC Specific Macro Expansion
#define PP_EXPAND_MSVC(x) x

#define PP_MACRO_OVERLOAD(NAME, ...) PP_EXPAND_MSVC(PP_OVERLOAD_SELECT(NAME, PP_VA_ARG_SIZE(__VA_ARGS__))(__VA_ARGS__)) 

Adding this extra expansion causes MSVC to properly expand __VA_ARGS__:

#define MSG_2(arg1, arg2) /* impl… */
LOG_2(1, 2)
Without the extra expansion it expands into:
Arg1= 1, 2
Arg2=

Instead of the proper expansion:
Arg1= 1
Arg2= 2

This solution nicely handles multiple args, however if you add any other defines to wrap the PP_MACRO_OVERLOAD function, MSVC cannot determine between 0 and 1 arg, so your _1 function will need to be capable of handling blank argument values. It’s easier to have your _1 handler support both _0 and _1 as the fix descends into a nightmare where the defined version will correctly preprocess but not compile, even if the exact same expanded code compiles when you write it without the preprocessor.

Further Reading:

Leave a Reply

Your email address will not be published. Required fields are marked *