I have a variadic macro M that takes a number of parameters separated by a special argument SEPARATOR:
M(a, b, c, SEPARATOR, 1, 2, 3);
I’d like to have two macros BEFORE and AFTER that I can call with all of M’s arguments and that will return me only the parameters on the left / right side of SEPARATOR respectively.
This means BEFORE(a, b, c, SEPARATOR, 1, 2, 3) should evaluate to a, b, c, whereas AFTER(a, b, c, SEPARATOR, 1, 2, 3) should evaluate to 1, 2, 3.
Based on http://jhnet.co.uk/articles/cpp_magic, I’ve come up with a solution that works for the examples I’ve given above:
#define FIRST(a, ...) a
#define SECOND(a, b, ...) b
#define EMPTY()
#define EVAL(...) EVAL1024(__VA_ARGS__)
#define EVAL1024(...) EVAL512(EVAL512(__VA_ARGS__))
#define EVAL512(...) EVAL256(EVAL256(__VA_ARGS__))
#define EVAL256(...) EVAL128(EVAL128(__VA_ARGS__))
#define EVAL128(...) EVAL64(EVAL64(__VA_ARGS__))
#define EVAL64(...) EVAL32(EVAL32(__VA_ARGS__))
#define EVAL32(...) EVAL16(EVAL16(__VA_ARGS__))
#define EVAL16(...) EVAL8(EVAL8(__VA_ARGS__))
#define EVAL8(...) EVAL4(EVAL4(__VA_ARGS__))
#define EVAL4(...) EVAL2(EVAL2(__VA_ARGS__))
#define EVAL2(...) EVAL1(EVAL1(__VA_ARGS__))
#define EVAL1(...) __VA_ARGS__
#define DEFER2(m) m EMPTY EMPTY()()
#define DEFER3(m) m EMPTY EMPTY EMPTY()()()
#define IS_PROBE(...) SECOND(__VA_ARGS__, 0)
#define PROBE() ~, 1
#define CAT(a,b) a ## b
#define NOT(x) IS_PROBE(CAT(_NOT_, x))
#define _NOT_0 PROBE()
#define BOOL(x) NOT(NOT(x))
#define IF_ELSE(condition) _IF_ELSE(BOOL(condition))
#define _IF_ELSE(condition) CAT(_IF_, condition)
#define _IF_1(...) __VA_ARGS__ _IF_1_ELSE
#define _IF_0(...) _IF_0_ELSE
#define _IF_1_ELSE(...)
#define _IF_0_ELSE(...) __VA_ARGS__
#define HAS_ARGS(...) BOOL(FIRST(_END_OF_ARGUMENTS_ __VA_ARGS__)())
#define _END_OF_ARGUMENTS_() 0
#define IS_SEPARATOR(x) IS_PROBE(CAT(_PROBE_, x))
#define _PROBE_SEPARATOR PROBE()
#define BEFORE_IMPL3(first, ...) IF_ELSE(IS_SEPARATOR(first))(/* do nothing */)(, first DEFER3(_BEFORE_IMPL2)()(__VA_ARGS__))
#define BEFORE_IMPL2(first, ...) IF_ELSE(HAS_ARGS(first))(BEFORE_IMPL3(first, __VA_ARGS__))()
#define _BEFORE_IMPL2() BEFORE_IMPL2
#define BEFORE_IMPL1(first, ...) IF_ELSE(IS_SEPARATOR(first))( /* do nothing */ )(first EVAL(BEFORE_IMPL2(__VA_ARGS__)))
#define BEFORE(first, ...) IF_ELSE(HAS_ARGS(first))(BEFORE_IMPL1(first, __VA_ARGS__))()
#define AFTER_IMPL2(first, ...) IF_ELSE(IS_SEPARATOR(first))(__VA_ARGS__)(DEFER3(_AFTER_IMPL)()(__VA_ARGS__))
#define AFTER_IMPL(first, ...) IF_ELSE(HAS_ARGS(first))(AFTER_IMPL2(first, __VA_ARGS__))( /* do nothing */ )
#define _AFTER_IMPL() AFTER_IMPL
#define AFTER(...) EVAL(AFTER_IMPL(__VA_ARGS__))
There is one problem with this solution, however: It breaks if one of the macro arguments starts with a preprocessor token like [, e.g. in M([[maybe_unused]] int a, foo(), SEPARATOR, "abc").
The reason for this is the following: the macro IS_SEPARATOR doesn’t work if its argument starts with a token like [ because it tries to concatenate that token with _PROBE_, (in IS_SEPARATOR([[maybe_unused]] int a)). This is not allowed and generates the following error:
error: pasting "_PROBE_" and "[" does not give a valid preprocessing token
Do you have any idea how I could fix IS_SEPARATOR to work in this situation? Or alternatively another solution how I could implement the BEFORE and AFTER macros?
Edit:
To reproduce the error, you can put all of the above in a file macros.cpp plus add the following lines with a dummy implementation of M (this will not evaluate to a valid program and my actual implementation is more complex, but this doesn’t matter here as my goal is to fix the preprocessor step for now):
#define M(...) BEFORE(__VA_ARGS__) && AFTER(__VA_ARGS__)
M([[maybe_unused]] int a, foo(), SEPARATOR, "abc")
Then run the preprocessor with:
g++ -E -P macros.cpp -std=c++17
The output I’d like to see for this is:
[[maybe_unused]] int a , foo() && "abc"
Instead, we get the above-mentioned error.
It becomes very easy if you do #define SEPARATOR )(:
#define SEPARATOR )(
#define BEFORE(...) BEFORE_1((__VA_ARGS__))
#define BEFORE_1(...) BEFORE_2 __VA_ARGS__ )
#define BEFORE_2(...) __VA_ARGS__ NULL(
#define NULL(...)
#define AFTER(...) AFTER_1((__VA_ARGS__))
#define AFTER_1(...) AFTER_2 __VA_ARGS__
#define AFTER_2(...) AFTER_3
#define AFTER_3(...) __VA_ARGS__
BEFORE(a, b, c, SEPARATOR, 1, 2, 3) // a,b,c,
AFTER(a, b, c, SEPARATOR, 1, 2, 3) // ,1,2,3
This pastes the commas before and after SEPARATOR to the output, but dealing with that is left as an exercise to the reader (perhaps just change your syntax to remove the commas?).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With