Inspired by some answers from this question, I've got the following C++ code that tries to emulate default function arguments with macros.
// do_func(int a, int b, int c = 0);
void do_func(int, int, int);
#define FUNC_2_ARGS(a, b)    do_func(a, b, 0)
#define FUNC_3_ARGS(a, b, c) do_func(a, b, c)
#define GET_4TH_ARG(arg1, arg2, arg3, arg4, ...) arg4
#define MACRO_CHOOSER(...) \
    GET_4TH_ARG(__VA_ARGS__, FUNC_3_ARGS, \
                FUNC_2_ARGS, not_func)
#define func(...) MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)
func(1, 2)
func(1, 2, 3)
The idea being that MACRO_CHOOSER(__VA_ARGS__) should expand to either GET_4TH_ARG(1, 2, 3, FUNC_3_ARGS, FUNC_2_ARGS, not_func which further expands to FUNC_2_ARGS which now takes (1, 2) and results into do_func(1, 2, 0).  Similarly, if three arguments are passed, FUNC_3_ARGS is chosen.
It works perfectly well with both gcc and clang when run like g++ -E x.cpp:
# 0 "x.cpp"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "x.cpp"
void do_func(int, int, int);
# 13 "x.cpp"
do_func(1, 2, 0)
do_func(1, 2, 3)
However, Visual C++ 19.32's seems to apply GET_4TH_ARG before expanding __VA_ARGS__ and always gets the following result when run with cl /P x.cpp:
#line 1 "x.cpp"
void do_func(int, int, int);
// snip
not_func(1, 2)
not_func(1, 2, 3)
What behavior is correct here according to C++ or C standard? Is it even a legal C++? Or maybe it's legal C, but not C++?
Of course, there are better macros and BOOST_PP, but I'm wondering what's the logic here.
 
    