Reputation: 509
I have read this article, which illustrates how the __VA_OPT__
function macro can be used to recursively expand a macro. I would like to implement something similar, with the difference being that the macro is expanded in a nested context.
The input:
NEST_RECURSIVE(A, B, C)
should produce (order is irrelevant):
((( | C) | B) | A)
My approach is slightly generalized from the article:
#define PARENS ()
#define EXPAND(...) EXPAND4(EXPAND4(EXPAND4(EXPAND4(__VA_ARGS__))))
#define EXPAND4(...) EXPAND3(EXPAND3(EXPAND3(EXPAND3(__VA_ARGS__))))
#define EXPAND3(...) EXPAND2(EXPAND2(EXPAND2(EXPAND2(__VA_ARGS__))))
#define EXPAND2(...) EXPAND1(EXPAND1(EXPAND1(EXPAND1(__VA_ARGS__))))
#define EXPAND1(...) __VA_ARGS__
#define FOR_EACH_R(func, ...) __VA_OPT__(EXPAND(FOR_EACH_HELPER_R(func, __VA_ARGS__)))
#define FOR_EACH_HELPER_R(func, sub, ...) func(__VA_OPT__(FOR_EACH_AGAIN_R PARENS (func, __VA_ARGS__)), sub)
#define FOR_EACH_AGAIN_R() FOR_EACH_HELPER_R
#define MY_FUNC(nested, var) (nested | var)
#define NEST_RECURSIVE(...) FOR_EACH_R(MY_FUNC, __VA_ARGS__)
The current code produces the following output:
(FOR_EACH_HELPER_R (MY_FUNC, B, C) | A)
As can be seen the expansion does not occur past the first level.
I imagine I'd have to EXPAND
somewhere else, however, I cannot figure out where.
It's entirely possible that what I'm looking to do is impossible, however, the pre-C++20 method of recursive macro expansion (utilizing PP_NARG
) does work with nesting, so I'd hope the new, cleaner, approach works with it as well!
Upvotes: 1
Views: 1007
Reputation: 10965
Your basic FOR_EACH_R
is correct, what's causing the problem is the call to func
within your FOR_EACH_HELPER_R
macro.
You can verify this by temporarely removing it:
/* ... */
#define FOR_EACH_HELPER_R(func, sub, ...) (__VA_OPT__(FOR_EACH_AGAIN_R PARENS (func, __VA_ARGS__)), sub)
/* ... */
NEST_RECURSIVE(A, B, C)
would result in:
(((, C), B), A)
Macro Evaluation is not very intuitive, so i'll quickly explain the 2 concepts that are important to this answer:
Example:
#define FOO() 1
#define BAR() 2
#define BAZ(A, B) MIAU(A, B)
#define MIAU(A, B) B A
BAZ(FOO(), BAR())
When the preprocessor sees the call to BAZ()
the following things will happen:
FOO()
-> 1BAR()
-> 2BAZ(1, 2)
-> MIAU(1, 2)
MIAU(1, 2)
-> 2 1
So arguments can be potentially evaluated twice - once before they are substituted into the macro body and then again when the body is evaluated.
This is also why you can pass macros as arguments to other macros, e.g.:
#define FOO(fn) fn()
#define BAR() 12
FOO(BAR)
Here the following things would happen:
BAR
does name a macro, but it's not a macro call. So the preprocessor will not evaluate it and treats it as text: BAR
FOO(BAR)
-> BAR()
BAR()
-> 12
Your EXPAND
macro also uses this to repeatedly force evaluation of an expression.
e.g.:
#define FOO 1 + FOO
FOO
#define BAR 1 + BAZ
#define BAZ BAR
BAR
FOO
would result in 1 + FOO
BAR
would result in 1 + BAR
Essentially while the preprocessor is evaluating a given macro, e.g. BAR
, any occurance of BAR
within it (or in one of the macros it calls) will be marked as don't try to evaluate this - EVER.
So basically once a macro sees it's own name it's game over.
Let's walk through the evaluation of your FOR_EACH_R
macro:
(i'll leave out the EXPAND
macros for simplicity)
EXPAND
roundFOR_EACH_HELPER_R(MY_FUNC, A, B, C)
MY_FUNC(FOR_EACH_AGAIN_R PARENS (MY_FUNC, B, C), A)
MY_FUNC
, so both arguments will be evaluated:
FOR_EACH_AGAIN_R PARENS (MY_FUNC, B, C)
becomes FOR_EACH_AGAIN_R () (MY_FUNC, B, C)
(not evaluated further due to FOR_EACH_AGAIN_R
not being a direct call)A
-> A
MY_FUNC
body:MY_FUNC((FOR_EACH_AGAIN_R () (MY_FUNC, B, C)), A)
(FOR_EACH_AGAIN_R () (MY_FUNC, B, C) | A)
(FOR_EACH_AGAIN_R () (MY_FUNC, B, C) | A)
(FOR_EACH_HELPER_R (MY_FUNC, B, C) | A)
MY_FUNC
which was called by FOR_EACH_HELPER_R
and we are trying to call FOR_EACH_HELPER_R
again here)FOR_EACH_HELPER_R
will be marked as don't try to evaluate thisMY_FUNC
parameter will also be marked (since we're in MY_FUNC
)EXPAND
round after that(FOR_EACH_HELPER_R (MY_FUNC, B, C) | A)
but FOR_EACH_HELPER_R
is marked as don't try to evaluate this, so the call is ignored and nothing gets replaced.-> You end up with (FOR_EACH_HELPER_R (MY_FUNC, B, C) | A)
as output
The big problem is that you pass FOR_EACH_AGAIN_R(...)
as an argument to your func
, which will evaluate that part twice (once as argument, once in the body), so the preprocessor sees the recursive call and stops.
You can partially fix it by delaying the FOR_EACH_AGAIN_R
by another evaluation cycle, e.g.:
/* ... */
#define FOR_EACH_HELPER_R(func, sub, ...) func (__VA_OPT__(FOR_EACH_AGAIN_R PARENS (func, __VA_ARGS__)), sub)
#define FOR_EACH_AGAIN_R() FOR_EACH_AGAIN_R_IMPL PARENS
#define FOR_EACH_AGAIN_R_IMPL() FOR_EACH_HELPER_R
/* ... */
This would result in:
(MY_FUNC (MY_FUNC (, C), B) | A)
Now the loop got fully expanded, however there's still a recursion problem with MY_FUNC
.
The underlying issue here is that one of the parameters to MY_FUNC
will contain MY_FUNC
, e.g.:
MY_FUNC((FOR_EACH_AGAIN_R PARENS (MY_FUNC, B, C)), A)
so once the preprocessor substitutes MY_FUNC
into MY_FUNC
that token will be immediatly marked as don't ever try to evaluate that again.
So the MY_FUNC
chain gets stuck after the first call.
It would be a lot easier if you wouldn't need recursive calls, e.g.:
/* ... */
#define FOR_EACH_HELPER_R(func, sub, ...) __VA_OPT__(FOR_EACH_AGAIN_R PARENS (func, __VA_ARGS__)), func(sub)
/* ... */
#define MY_FUNC(var) (var)
/* ... */
would work without problems (result would be , (C), (B), (A)
)
If you absolutely need the recursive calls, then there is only one way:
You need to make sure that MY_FUNC
never gets to see FOR_EACH_HELPER_R
& MY_FUNC
.
But given that each invocation of MY_FUNC
needs the result of the previous invocation, your only option is to build the macro in such a way that all the MY_FUNC
calls are evaluated at once.
e.g. you need to build FOR_EACH_HELPER_R
in such a way that eventually you're left with:
MY_FUNC(MY_FUNC(MY_FUNC(, C), B), A)
so that the recursive calls will be evaluated correctly.
The easiest way to ensure this is to use the same delay trick you use for FOR_EACH_AGAIN_R
, e.g. with a set of macros like this:
#define DELAY6(...) DELAY6_IMPL PARENS __VA_OPT__((__VA_ARGS__))
#define DELAY5(...) DELAY5_IMPL PARENS __VA_OPT__((__VA_ARGS__))
#define DELAY4(...) DELAY4_IMPL PARENS __VA_OPT__((__VA_ARGS__))
#define DELAY3(...) DELAY3_IMPL PARENS __VA_OPT__((__VA_ARGS__))
#define DELAY2(...) DELAY2_IMPL PARENS __VA_OPT__((__VA_ARGS__))
#define DELAY1(...) DELAY1_IMPL PARENS __VA_OPT__((__VA_ARGS__))
#define DELAY0(...) __VA_ARGS__
#define DELAY6_IMPL() DELAY5
#define DELAY5_IMPL() DELAY4
#define DELAY4_IMPL() DELAY3
#define DELAY3_IMPL() DELAY2
#define DELAY2_IMPL() DELAY1
#define DELAY1_IMPL() DELAY0
So DELAY6
will delay it for 6 evaluations, DELAY5
for 5, etc...
Then you can use it to delay the call to MY_FUNC
, e.g.:
#define FOR_EACH_R(func, ...) __VA_OPT__(EXPAND(FOR_EACH_HELPER_R(func, DELAY6, __VA_ARGS__)))
#define FOR_EACH_HELPER_R(func, del, sub, ...) del(func) (__VA_OPT__(FOR_EACH_AGAIN_R PARENS (func, del(), __VA_ARGS__)), sub)
notice that we're passing del()
, not del
to the next iteration of FOR_EACH_HELPER_R
, this will effectively result in the next lower DELAY*
function being passed (so that all the delays resolve in a single evaluation)
With NEST_RECURSIVE(A, B, C, D, E, F, G)
this would evaluate as following:
DELAY5_IMPL () (MY_FUNC) (DELAY5_IMPL () (MY_FUNC) (FOR_EACH_AGAIN_R () (MY_FUNC, DELAY5_IMPL () , C, D, E, F, G), B), A)
->
DELAY4_IMPL () (MY_FUNC) (DELAY4_IMPL () (MY_FUNC) (DELAY4_IMPL () (MY_FUNC) (FOR_EACH_AGAIN_R () (MY_FUNC, DELAY4_IMPL () , D, E, F, G), C), B), A)
->
DELAY3_IMPL () (MY_FUNC) (DELAY3_IMPL () (MY_FUNC) (DELAY3_IMPL () (MY_FUNC) (DELAY3_IMPL () (MY_FUNC) (FOR_EACH_AGAIN_R () (MY_FUNC, DELAY3_IMPL () , E, F, G), D), C), B), A)
->
DELAY2_IMPL () (MY_FUNC) (DELAY2_IMPL () (MY_FUNC) (DELAY2_IMPL () (MY_FUNC) (DELAY2_IMPL () (MY_FUNC) (DELAY2_IMPL () (MY_FUNC) (FOR_EACH_AGAIN_R () (MY_FUNC, DELAY2_IMPL () , F, G), E), D), C), B), A)
->
DELAY1_IMPL () (MY_FUNC) (DELAY1_IMPL () (MY_FUNC) (DELAY1_IMPL () (MY_FUNC) (DELAY1_IMPL () (MY_FUNC) (DELAY1_IMPL () (MY_FUNC) (DELAY1_IMPL () (MY_FUNC) (FOR_EACH_AGAIN_R () (MY_FUNC, DELAY1_IMPL () , G), F), E), D), C), B), A)
->
((((((( | G) | F) | E) | D) | C) | B) | A)
Notice that MY_FUNC
didn't get called until the last evaluation round - this essentially ensures that all MY_FUNC calls are evaluated at once and we don't get any problems with recursive macro calls.
You'll have to define a lot of DELAY_
macros though for this to work (1 more delay macro for each additional parameter to FOR_EACH_R
)
Full code example: godbolt
#define PARENS ()
#define EXPAND(...) EXPAND4(EXPAND4(EXPAND4(EXPAND4(__VA_ARGS__))))
#define EXPAND4(...) EXPAND3(EXPAND3(EXPAND3(EXPAND3(__VA_ARGS__))))
#define EXPAND3(...) EXPAND2(EXPAND2(EXPAND2(EXPAND2(__VA_ARGS__))))
#define EXPAND2(...) EXPAND1(EXPAND1(EXPAND1(EXPAND1(__VA_ARGS__))))
#define EXPAND1(...) __VA_ARGS__
#define DELAY6(...) DELAY6_IMPL PARENS __VA_OPT__((__VA_ARGS__))
#define DELAY5(...) DELAY5_IMPL PARENS __VA_OPT__((__VA_ARGS__))
#define DELAY4(...) DELAY4_IMPL PARENS __VA_OPT__((__VA_ARGS__))
#define DELAY3(...) DELAY3_IMPL PARENS __VA_OPT__((__VA_ARGS__))
#define DELAY2(...) DELAY2_IMPL PARENS __VA_OPT__((__VA_ARGS__))
#define DELAY1(...) DELAY1_IMPL PARENS __VA_OPT__((__VA_ARGS__))
#define DELAY0(...) __VA_ARGS__
#define DELAY6_IMPL() DELAY5
#define DELAY5_IMPL() DELAY4
#define DELAY4_IMPL() DELAY3
#define DELAY3_IMPL() DELAY2
#define DELAY2_IMPL() DELAY1
#define DELAY1_IMPL() DELAY0
#define FOR_EACH_R(func, ...) __VA_OPT__(EXPAND(FOR_EACH_HELPER_R(func, DELAY6, __VA_ARGS__)))
#define FOR_EACH_HELPER_R(func, del, sub, ...) del(func) (__VA_OPT__(FOR_EACH_AGAIN_R PARENS (func, del(), __VA_ARGS__)), sub)
#define FOR_EACH_AGAIN_R() FOR_EACH_HELPER_R
#define MY_FUNC(nested, var) (nested | var)
#define NEST_RECURSIVE(...) FOR_EACH_R(MY_FUNC, __VA_ARGS__)
NEST_RECURSIVE(A, B, C, D, E, F, G)
The above solution requires a very good understanding of how macros are evaluated to understand what's happending. You could also choose an easier approach by just defining a bunch of macros, like boost does for example
e.g.:
#define FOR_EACH_ERROR()
#define FOR_EACH_R(fn, ...) __VA_OPT__(FOR_EACH_R_IMPL_0(fn, __VA_ARGS__))
#define FOR_EACH_R_IMPL_0(fn, el, ...) fn(__VA_OPT__(FOR_EACH_R_IMPL_1(fn, __VA_ARGS__)), el)
#define FOR_EACH_R_IMPL_1(fn, el, ...) fn(__VA_OPT__(FOR_EACH_R_IMPL_2(fn, __VA_ARGS__)), el)
#define FOR_EACH_R_IMPL_2(fn, el, ...) fn(__VA_OPT__(FOR_EACH_R_IMPL_3(fn, __VA_ARGS__)), el)
#define FOR_EACH_R_IMPL_3(fn, el, ...) fn(__VA_OPT__(FOR_EACH_R_IMPL_4(fn, __VA_ARGS__)), el)
#define FOR_EACH_R_IMPL_4(fn, el, ...) fn(__VA_OPT__(FOR_EACH_R_IMPL_5(fn, __VA_ARGS__)), el)
#define FOR_EACH_R_IMPL_5(fn, el, ...) fn(__VA_OPT__(FOR_EACH_R_IMPL_6(fn, __VA_ARGS__)), el)
#define FOR_EACH_R_IMPL_6(fn, el, ...) fn(__VA_OPT__(FOR_EACH_R_IMPL_7(fn, __VA_ARGS__)), el)
#define FOR_EACH_R_IMPL_7(fn, el, ...) fn(__VA_OPT__(FOR_EACH_R_IMPL_8(fn, __VA_ARGS__)), el)
#define FOR_EACH_R_IMPL_8(fn, el, ...) fn(__VA_OPT__(FOR_EACH_R_IMPL_9(fn, __VA_ARGS__)), el)
#define FOR_EACH_R_IMPL_9(fn, el, ...) fn(__VA_OPT__(FOR_EACH_R_IMPL_10(fn, __VA_ARGS__)), el)
#define FOR_EACH_R_IMPL_10(...) FOR_EACH_ERROR("Shenanigans!")
#define MY_FUNC(nested, var) (nested | var)
#define NEST_RECURSIVE(...) FOR_EACH_R(MY_FUNC, __VA_ARGS__)
NEST_RECURSIVE(A, B, C, D, E, F, G)
this is easier to understand & also very easy to expand (just add more macros)
If you want a recursive version to reduce the number of lines you need to write, you could accomplish that by using a left fold, e.g.:
// Recursive Left Fold
#define FOR_EACH_L(fn, ...) __VA_OPT__(FOR_EACH_APPLY0(FOR_EACH_RESULT, FOR_EACH_L_4(fn,,__VA_ARGS__)))
#define FOR_EACH_L_4(fn, res, ...) FOR_EACH_APPLY4(FOR_EACH_L_3, FOR_EACH_APPLY4(FOR_EACH_L_3, FOR_EACH_APPLY4(FOR_EACH_L_3, fn, res __VA_OPT__(, __VA_ARGS__))))
#define FOR_EACH_L_3(fn, res, ...) FOR_EACH_APPLY3(FOR_EACH_L_2, FOR_EACH_APPLY3(FOR_EACH_L_2, FOR_EACH_APPLY3(FOR_EACH_L_2, fn, res __VA_OPT__(, __VA_ARGS__))))
#define FOR_EACH_L_2(fn, res, ...) FOR_EACH_APPLY2(FOR_EACH_L_1, FOR_EACH_APPLY2(FOR_EACH_L_1, FOR_EACH_APPLY2(FOR_EACH_L_1, fn, res __VA_OPT__(, __VA_ARGS__))))
#define FOR_EACH_L_1(fn, res, ...) FOR_EACH_APPLY1(FOR_EACH_L_0, FOR_EACH_APPLY1(FOR_EACH_L_0, FOR_EACH_APPLY1(FOR_EACH_L_0, fn, res __VA_OPT__(, __VA_ARGS__))))
#define FOR_EACH_L_0(fn, res, ...) fn, FOR_EACH_FIRST(__VA_OPT__(fn(res, FOR_EACH_FIRST(__VA_ARGS__)), ) res) __VA_OPT__(FOR_EACH_TAIL(__VA_ARGS__))
#define FOR_EACH_APPLY4(fn, ...) fn(__VA_ARGS__)
#define FOR_EACH_APPLY3(fn, ...) fn(__VA_ARGS__)
#define FOR_EACH_APPLY2(fn, ...) fn(__VA_ARGS__)
#define FOR_EACH_APPLY1(fn, ...) fn(__VA_ARGS__)
#define FOR_EACH_APPLY0(fn, ...) fn(__VA_ARGS__)
#define FOR_EACH_FIRST(el, ...) el
#define FOR_EACH_TAIL(el, ...) __VA_OPT__(, __VA_ARGS__)
#define FOR_EACH_RESULT(fn, res, ...) res
Left-folds are easier to implement than right-folds, because getting the first element of __VA_ARGS__
is a lot easier than getting the last one (FOR_EACH_FIRST
in the above example).
The version provided above can handle up to 81 parameters, if you need more just create more versions of the FOR_EACH_L_*
& FOR_EACH_APPLY*
macros. (each additional one of those macros triples the max number of arguments it can handle)
You do need to provide the arguments in reverse order though, since it's a left fold, e.g.:
NEST_RECURSIVE(A, B, C, D, E, F, G)
// would result in ((((((( | A) | B) | C) | D) | E) | F) | G)
If you need a right fold you could implement it by reversing the arguments and then calling the left fold variant we created above.
e.g.:
// Reverse args
#define PARENS ()
#define EXPAND(...) EXPAND4(EXPAND4(EXPAND4(EXPAND4(__VA_ARGS__))))
#define EXPAND4(...) EXPAND3(EXPAND3(EXPAND3(EXPAND3(__VA_ARGS__))))
#define EXPAND3(...) EXPAND2(EXPAND2(EXPAND2(EXPAND2(__VA_ARGS__))))
#define EXPAND2(...) EXPAND1(EXPAND1(EXPAND1(EXPAND1(__VA_ARGS__))))
#define EXPAND1(...) __VA_ARGS__
#define REVERSE(...) __VA_OPT__(EXPAND(REVERSE_HELPER(__VA_ARGS__)))
#define REVERSE_HELPER(el, ...) __VA_OPT__(REVERSE_AGAIN PARENS (__VA_ARGS__), ) el
#define REVERSE_AGAIN() REVERSE_HELPER
(this also only works for up to 81 arguments, you can add more EXPAND*
macros to increase the number of args it can handle)
example:
REVERSE(A, B, C, D, E, F, G)
// would result in G, F, E, D, C, B, A
and then you could implement the right fold like this:
// Right fold
#define FOR_EACH_R(fn, ...) __VA_OPT__(FOR_EACH_R_APPLY(FOR_EACH_L, fn, REVERSE(__VA_ARGS__)))
#define FOR_EACH_R_APPLY(fn, ...) fn(__VA_ARGS__)
which finally gives you the expected result (for up to 81 arguments), e.g.:
NEST_RECURSIVE(A, B, C, D, E, F, G)
// would result in: ((((((( | G) | F) | E) | D) | C) | B) | A)
Full Code: godbolt
// Reverse args
#define PARENS ()
#define EXPAND(...) EXPAND4(EXPAND4(EXPAND4(EXPAND4(__VA_ARGS__))))
#define EXPAND4(...) EXPAND3(EXPAND3(EXPAND3(EXPAND3(__VA_ARGS__))))
#define EXPAND3(...) EXPAND2(EXPAND2(EXPAND2(EXPAND2(__VA_ARGS__))))
#define EXPAND2(...) EXPAND1(EXPAND1(EXPAND1(EXPAND1(__VA_ARGS__))))
#define EXPAND1(...) __VA_ARGS__
#define REVERSE(...) __VA_OPT__(EXPAND(REVERSE_HELPER(__VA_ARGS__)))
#define REVERSE_HELPER(el, ...) __VA_OPT__(REVERSE_AGAIN PARENS (__VA_ARGS__), ) el
#define REVERSE_AGAIN() REVERSE_HELPER
// Recursive Left Fold
#define FOR_EACH_L(fn, ...) __VA_OPT__(FOR_EACH_APPLY0(FOR_EACH_RESULT, FOR_EACH_L_4(fn,,__VA_ARGS__)))
#define FOR_EACH_L_4(fn, res, ...) FOR_EACH_APPLY4(FOR_EACH_L_3, FOR_EACH_APPLY4(FOR_EACH_L_3, FOR_EACH_APPLY4(FOR_EACH_L_3, fn, res __VA_OPT__(, __VA_ARGS__))))
#define FOR_EACH_L_3(fn, res, ...) FOR_EACH_APPLY3(FOR_EACH_L_2, FOR_EACH_APPLY3(FOR_EACH_L_2, FOR_EACH_APPLY3(FOR_EACH_L_2, fn, res __VA_OPT__(, __VA_ARGS__))))
#define FOR_EACH_L_2(fn, res, ...) FOR_EACH_APPLY2(FOR_EACH_L_1, FOR_EACH_APPLY2(FOR_EACH_L_1, FOR_EACH_APPLY2(FOR_EACH_L_1, fn, res __VA_OPT__(, __VA_ARGS__))))
#define FOR_EACH_L_1(fn, res, ...) FOR_EACH_APPLY1(FOR_EACH_L_0, FOR_EACH_APPLY1(FOR_EACH_L_0, FOR_EACH_APPLY1(FOR_EACH_L_0, fn, res __VA_OPT__(, __VA_ARGS__))))
#define FOR_EACH_L_0(fn, res, ...) fn, FOR_EACH_FIRST(__VA_OPT__(fn(res, FOR_EACH_FIRST(__VA_ARGS__)), ) res) __VA_OPT__(FOR_EACH_TAIL(__VA_ARGS__))
#define FOR_EACH_APPLY4(fn, ...) fn(__VA_ARGS__)
#define FOR_EACH_APPLY3(fn, ...) fn(__VA_ARGS__)
#define FOR_EACH_APPLY2(fn, ...) fn(__VA_ARGS__)
#define FOR_EACH_APPLY1(fn, ...) fn(__VA_ARGS__)
#define FOR_EACH_APPLY0(fn, ...) fn(__VA_ARGS__)
#define FOR_EACH_FIRST(el, ...) el
#define FOR_EACH_TAIL(el, ...) __VA_OPT__(, __VA_ARGS__)
#define FOR_EACH_RESULT(fn, res, ...) res
// Right fold
#define FOR_EACH_R(fn, ...) __VA_OPT__(FOR_EACH_R_APPLY(FOR_EACH_L, fn, REVERSE(__VA_ARGS__)))
#define FOR_EACH_R_APPLY(fn, ...) fn(__VA_ARGS__)
// For testing
#define MY_FUNC(nested, var) (nested | var)
#define NEST_RECURSIVE(...) FOR_EACH_R(MY_FUNC, __VA_ARGS__)
NEST_RECURSIVE(A, B, C, D, E, F, G)
Upvotes: 7