Assuming C++20 is available to you and you don't mind somewhat complicated macros you could use a recursive macro utilizing the new __VA_OPT__
token and deferred expressions, which in combination allow (limited) recursion within macros.
Note: C++20 and __VA_OPT__
are not strictly required, but they greatly simplify the implementation.
In your case it would allow you to write something like this:
godbolt
#define GEN(x, y, z) template bool match_expr<x,y,z>();
FOR_EACH_COMBINATION(
GEN,
(1, 2, 3),
(real, bool, index),
(0, 1)
)
#undef GEN
which would expand to:
template bool match_expr<1,real,0>();
template bool match_expr<1,real,1>();
template bool match_expr<1,bool,0>();
template bool match_expr<1,bool,1>();
template bool match_expr<1,index,0>();
template bool match_expr<1,index,1>();
template bool match_expr<2,real,0>();
template bool match_expr<2,real,1>();
template bool match_expr<2,bool,0>();
template bool match_expr<2,bool,1>();
template bool match_expr<2,index,0>();
template bool match_expr<2,index,1>();
template bool match_expr<3,real,0>();
template bool match_expr<3,real,1>();
template bool match_expr<3,bool,0>();
template bool match_expr<3,bool,1>();
template bool match_expr<3,index,0>();
template bool match_expr<3,index,1>();
1. How recursive macros work
Here's a good read that explains recursive macros in detail. (or a shorter version here)
The basic gist of it is that you can force an expression to take multiple preprocessor scans to be fully evaluated, e.g.:
godbolt
#define FOO(a) [a]
#define DELAY DELAY_IMPL_NOTHING
#define DELAY_IMPL_NOTHING()
// normal function-like macro expansion
FOO(bar) // => [bar]
// function-like macro will not expand, due to delay
FOO DELAY() (bar) // => FOO (bar)
while also being able to force an additional scan on any expression by simply passing it to a function-like macro:
godbolt
#define EXPAND(...) __VA_ARGS__
// forcing a rescan on the expression will expand the function-like macro
EXPAND(FOO DELAY() (bar)) // => [bar]
Those two things in combination allow us to write a "recursive" macro:
godbolt
#define RECURSE(fn, value) RECURSE_AGAIN DELAY() () (fn, fn(value))
#define RECURSE_AGAIN() RECURSE
RECURSE(FOO, bar) // => RECURSE_AGAIN () (FOO, [bar])
EXPAND(RECURSE(FOO, bar)) // => RECURSE_AGAIN () (FOO, [[bar]])
EXPAND(EXPAND(RECURSE(FOO, bar))) // => RECURSE_AGAIN () (FOO, [[[bar]]])
EXPAND(EXPAND(EXPAND(RECURSE(FOO, bar)))) // => RECURSE_AGAIN () (FOO, [[[[bar]]]])
Note that expansion of RECURSE
will not result directly in a recursive invocation of itself. (using RECURSE
directly within RECURSE
is impossible because the preprocessor does not allow macro-recursion)
The RECURSE
macro instead produces a delayed expression, that will only result in another RECURSE
expansion when another preprocessor scan happens. So the second call to RECURSE
will happen inside EXPAND
, after the original RECURSE
invocation has already been expanded.
But there's currently no way to stop the recursion of RECURSE
- this is where __VA_OPT__
comes in to save the day; __VA_OPT__ ( content )
expands to content
if __VA_ARGS__
is non-empty, otherwise it expands to nothing. (which we can use to end recursion once all parameters have been processed)
A simple FOR_EACH
implementation using recursive macros could look like this:
godbolt
// forces 3 rescans (2 invocations of EXPAND + 1 for EXPAND2)
#define EXPAND2(...) EXPAND(EXPAND(__VA_ARGS__))
#define FOR_EACH(fn, ...) \
__VA_OPT__(EXPAND2(FOR_EACH_IMPL(fn, __VA_ARGS__)))
#define FOR_EACH_IMPL(fn, first, ...) \
fn(first) \
__VA_OPT__( \
FOR_EACH_IMPL_AGAIN DELAY() () (fn, __VA_ARGS__) \
)
#define FOR_EACH_IMPL_AGAIN() FOR_EACH_IMPL
FOR_EACH(FOO, a, b) // => [a] [b]
FOR_EACH(FOO, a, b, c, d) // => [a] [b] [c] [d]
__VA_OPT__
is used within FOR_EACH_IMPL
to stop the recursion once we have called fn
for each argument.
The only limiting factor of this approach is the number of additional rescans your macro's provide (e.g. EXPAND
only provides 1, EXPAND2
provides 3).
But this can easily be solved by adding a few additional layers of expansion, e.g. this EXPAND_BIG
macro would already provide 86 rescans:
#define EXPAND_BIG(...) EXPAND1(EXPAND1(EXPAND1(EXPAND1(__VA_ARGS__))))
#define EXPAND1(...) EXPAND2(EXPAND2(EXPAND2(EXPAND2(__VA_ARGS__))))
#define EXPAND2(...) EXPAND3(EXPAND3(EXPAND3(EXPAND3(__VA_ARGS__))))
#define EXPAND3(...) __VA_ARGS__
Adding another EXPAND*
macro to EXPAND_BIG
would result in 342 rescans, so each layer more than quadruples the number of recursive calls you can use.
Note that this does have a cost, namely compile-time. The larger the EXPAND
macro stack, the longer it'll take to compile, so its best to use the lowest number of rescans that works for your use-case.
So with enough rescans you can basically do anything within macros, like iteration (see FOR_EACH
above), left folds (godbolt; explanation), or like in your case creating all possible combinations of multiple sets.
2. FOR_EACH_COMBINATION
implementation
2.1 The code
First we'll need a few utility-macros (mostly the ones from above + a few additional ones):
// Simple concat
// example: CONCAT(foo,bar) => foobar
#define CONCAT(a, b) CONCAT_IMPL(a, b)
#define CONCAT_IMPL(a, b) a##b
// returns the first argument
// example: VAARGS_HEAD(1,2,3) => 1
#define VAARGS_HEAD(head, ...) head
// returns all arguments except the first one
// example: VAARGS_TAIL(1,2,3) => 2,3
#define VAARGS_TAIL(head, ...) __VA_ARGS__
// basic preprocessor if
// examples:
// - IIF(1)(a,b) => a
// - IIF(0)(a,b) => b
#define IIF(value) CONCAT(IIF_,value)
#define IIF_1(true_, false_) true_
#define IIF_0(true_, false_) false_
// evaluates to 1 if it has been called with at least 1 argument, 0 otherwise
// examples:
// - HAS_VAARGS(1,2) => 1
// - HAS_VAARGS() => 0
#define HAS_VAARGS(...) VAARGS_HEAD(__VA_OPT__(1,) 0)
// forces the preprocessor to repeatedly scan an expression
// this definition forces a total of 86 scans, but can easily extended
// by adding more EXPAND*() macros (each additional one more than
// quadruples the amount of scans)
// examples:
// - CONCAT DELAY() (a,b) => CONCAT (a,b)
// - EXPAND(CONCAT DELAY() (a,b)) => ab
#define EXPAND(...) EXPAND1(EXPAND1(EXPAND1(EXPAND1(__VA_ARGS__))))
#define EXPAND1(...) EXPAND2(EXPAND2(EXPAND2(EXPAND2(__VA_ARGS__))))
#define EXPAND2(...) EXPAND3(EXPAND3(EXPAND3(EXPAND3(__VA_ARGS__))))
#define EXPAND3(...) __VA_ARGS__
// evaluates to nothing, but requires an additional preprocessor scan.
// this can be used to delay macro evaluations.
// examples:
// - CONCAT(a,b) => ab
// - CONCAT DELAY() (a,b) => a DELAY_IMPL_NOTHING () b
// - EXPAND(CONCAT DELAY() (a,b)) => ab
#define DELAY DELAY_IMPL_NOTHING
#define DELAY_IMPL_NOTHING()
// discards all arguments, evaluates to nothing
#define SWALLOW(...)
// appends an element to a tuple
// examples:
// - TUPLE_APPEND((a,b), c) => (a,b,c)
// - TUPLE_APPEND((), a) => (a)
#define TUPLE_APPEND(tuple, el) (TUPLE_APPEND_IMPL_UNPACK tuple el)
#define TUPLE_APPEND_IMPL_UNPACK(...) __VA_ARGS__ __VA_OPT__(,)
utilizing those macros we can then build the FOR_EACH_COMBINATION
macro:
// if __VA_ARGS__ is empty then it expands to fn(args);
// otherwise it'll expand to FOR_EACH_COMBINATION_IMPL_RECURSE(fn, args, __VA_ARGS__)
#define FOR_EACH_COMBINATION_IMPL(fn, args, ...) \
IIF(HAS_VAARGS(__VA_ARGS__))( \
FOR_EACH_COMBINATION_IMPL_RECURSE, \
FOR_EACH_COMBINATION_IMPL_CALL \
)(fn, args __VA_OPT__(, __VA_ARGS__))
// evaluates the user-provided function-like macro fn with arguments args.
// example: FOR_EACH_IMPL_CALL(foo, (1,2)) => foo(1,2)
#define FOR_EACH_COMBINATION_IMPL_CALL(fn, args) \
fn args
// if tuple has at least 1 element it calls FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY;
// otherwise it stops recursion.
// examples:
// - FOR_EACH_COMBINATION_IMPL_RECURSE(fn, (), (a, b))
// => FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY(fn, (), (a,b))
// - FOR_EACH_COMBINATION_IMPL_RECURSE(fn, (), ())
// =>
#define FOR_EACH_COMBINATION_IMPL_RECURSE(fn, args, tuple, ...) \
IIF(HAS_VAARGS tuple)( \
FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY, \
SWALLOW \
) DELAY() ( \
fn, args, tuple __VA_OPT__(, __VA_ARGS__) \
)
// calls FOR_EACH_COMBINATION_IMPL twice;
// once with the first element of tuple appended to args,
// and a second time with the first element of tuple removed.
// examples:
// - FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY(fn, (), (a,b), (c,d))
// => FOR_EACH_COMBINATION_IMPL(fn, (a), (c,d))
// FOR_EACH_COMBINATION_IMPL(fn, (), (b), (c,d))
#define FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY(fn, args, tuple, ...) \
FOR_EACH_COMBINATION_IMPL DELAY() ( \
fn, \
TUPLE_APPEND(args, VAARGS_HEAD tuple) \
__VA_OPT__(, __VA_ARGS__) \
) \
\
FOR_EACH_COMBINATION_IMPL DELAY() ( \
fn, \
args, \
(VAARGS_TAIL tuple) \
__VA_OPT__(, __VA_ARGS__) \
)
// takes a function-like macro (fn) and an arbitrary amount of tuples.
// the tuples can be of any size (only constrained by the number
// of expansions provided by the EXPAND macro)
// fn will be evaluated for each possible combination from the tuples.
// examples:
// - FOR_EACH_COMBINATION(foo, (a,b))
// => foo(a) foo(b)
// - FOR_EACH_COMBINATION(foo, (a,b), (1,2))
// => foo(a, 1) foo(a, 2) foo(b, 1) foo(b, 2)
#define FOR_EACH_COMBINATION(fn, ...) \
EXPAND( \
FOR_EACH_COMBINATION_IMPL( \
fn, \
() \
__VA_OPT__(, __VA_ARGS__) \
) \
)
2.2 How it works
FOR_EACH_COMBINATION_IMPL
is the core of this macro.
fn
is the user-defined function that will be called for each combination from the passed in tuples
args
is a tuple which is used to collect the arguments we need to pass to fn
__VA_ARGS__
are the tuples from which we still need to pick an element from.
Each call to FOR_EACH_COMBINATION_IMPL
can result in two different cases:
Here's an example expansion to illustrate the expansions happening:
FOR_EACH_COMBINATION(foo, (a, b), (1, 2))
=>
FOR_EACH_COMBINATION_IMPL(foo, (), (a, b), (1, 2))
=>
FOR_EACH_COMBINATION_IMPL(foo, (a), (1, 2))
FOR_EACH_COMBINATION_IMPL(foo, (), (b), (1, 2))
=>
FOR_EACH_COMBINATION_IMPL(foo, (a, 1))
FOR_EACH_COMBINATION_IMPL(foo, (a), (2))
FOR_EACH_COMBINATION_IMPL(foo, (b), (1, 2))
FOR_EACH_COMBINATION_IMPL(foo, (), (), (1, 2)) // expands to nothing
=>
foo(a, 1)
FOR_EACH_COMBINATION_IMPL(foo, (a, 2))
FOR_EACH_COMBINATION_IMPL(foo, (a), ()) // expands to nothing
FOR_EACH_COMBINATION_IMPL(foo, (b, 1))
FOR_EACH_COMBINATION_IMPL(foo, (b), (2))
=>
foo(a, 1)
foo(a, 2)
foo(b, 1)
FOR_EACH_COMBINATION_IMPL(foo, (b, 2))
FOR_EACH_COMBINATION_IMPL(foo, (b), ()) // expands to nothing
=>
foo(a, 1)
foo(a, 2)
foo(b, 1)
foo(b, 2)
2.3 Examples
Here are a few examples of FOR_EACH_COMBINATION
.
Note that you can pass as many tuples as you want and each tuple can contain as many elements as you want (the only constraining factor to size is how many rescans your EXPAND
macro provides)
2.3.1 Simple loop
#define GEN(x) template void do_something<x>();
FOR_EACH_COMBINATION(
GEN,
(1,2,3)
)
#undef GEN
expands to:
template void do_something<1>();
template void do_something<2>();
template void do_something<3>();
2.3.2 Your match_expr
function
// helper for getting a tuple of n consecutive int values, starting at 1.
// currently only works up to 5, but can be easily expanded by
// adding more INT_TUPLE_* macros.
// examples:
// - INT_TUPLE(0) => ()
// - INT_TUPLE(3) => (1,2,3)
// - INT_TUPLE(5) => (1,2,3,4,5)
#define INT_TUPLE(size) (CONCAT(INT_TUPLE_, size))
#define INT_TUPLE_0
#define INT_TUPLE_1 1
#define INT_TUPLE_2 INT_TUPLE_1, 2
#define INT_TUPLE_3 INT_TUPLE_2, 3
#define INT_TUPLE_4 INT_TUPLE_3, 4
#define INT_TUPLE_5 INT_TUPLE_4, 5
#define GEN(x, y, z) template bool match_expr<x,y,z>();
FOR_EACH_COMBINATION(
GEN,
INT_TUPLE(3),
(real, bool, index),
(0, 1)
)
#undef GEN
expands to
template bool match_expr<1,real,0>();
template bool match_expr<1,real,1>();
template bool match_expr<1,bool,0>();
template bool match_expr<1,bool,1>();
template bool match_expr<1,index,0>();
template bool match_expr<1,index,1>();
/* ... */
template bool match_expr<3,real,0>();
template bool match_expr<3,real,1>();
template bool match_expr<3,bool,0>();
template bool match_expr<3,bool,1>();
template bool match_expr<3,index,0>();
template bool match_expr<3,index,1>();
2.3.3 All 8bit patterns
// all possible arrangements of 8 bits, in order.
// all8BitPatterns[1] == {0,0,0,0,0,0,0,1}
// all8BitPatterns[4] == {0,0,0,0,0,1,0,0}
// all8BitPatterns[9] == {0,0,0,0,1,0,0,1}
// etc..
#define GEN(b1,b2,b3,b4,b5,b6,b7,b8) {b1,b2,b3,b4,b5,b6,b7,b8},
int all8BitPatterns[256][8] = {
FOR_EACH_COMBINATION(
GEN,
(0, 1), (0, 1), (0, 1), (0, 1),
(0, 1), (0, 1), (0, 1), (0, 1)
)
};
#undef GEN
expands to
int all8BitPatterns[256][8] = {
{0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,1},
{0,0,0,0,0,0,1,0},
{0,0,0,0,0,0,1,1},
/* ... */
{1,1,1,1,1,1,0,0},
{1,1,1,1,1,1,0,1},
{1,1,1,1,1,1,1,0},
{1,1,1,1,1,1,1,1},
};
3. Try it online!
Here's a godbolt that contains the FOR_EACH_COMBINATION
macro & all the examples shown above.
3.1 ... or local
Here's the full code, including all examples from above.
To just see the output from the preprocessor you can use those options:
- For gcc & clang:
-std=c++20 -E
- For msvc:
/std:c++20 /Zc:preprocessor /P
Warning: The msvc-preprocessor is not standard-compliant by default, /Zc:preprocessor
must be passed to force standard-compliant macro evaluation.
// Simple concat
// example: CONCAT(foo,bar) => foobar
#define CONCAT(a, b) CONCAT_IMPL(a, b)
#define CONCAT_IMPL(a, b) a##b
// returns the first argument
// example: VAARGS_HEAD(1,2,3) => 1
#define VAARGS_HEAD(head, ...) head
// returns all arguments except the first one
// example: VAARGS_TAIL(1,2,3) => 2,3
#define VAARGS_TAIL(head, ...) __VA_ARGS__
// basic preprocessor if
// examples:
// - IIF(1)(a,b) => a
// - IIF(0)(a,b) => b
#define IIF(value) CONCAT(IIF_,value)
#define IIF_1(true_, false_) true_
#define IIF_0(true_, false_) false_
// evaluates to 1 if it has been called with at least 1 argument, 0 otherwise
// examples:
// - HAS_VAARGS(1,2) => 1
// - HAS_VAARGS() => 0
#define HAS_VAARGS(...) VAARGS_HEAD(__VA_OPT__(1,) 0)
// forces the preprocessor to repeatedly scan an expression
// this definition forces a total of 86 scans, but can easily extended
// by adding more EXPAND*() macros (each additional one more than
// quadruples the amount of scans)
// examples:
// - CONCAT DELAY() (a,b) => CONCAT (a,b)
// - EXPAND(CONCAT DELAY() (a,b)) => ab
#define EXPAND(...) EXPAND1(EXPAND1(EXPAND1(EXPAND1(__VA_ARGS__))))
#define EXPAND1(...) EXPAND2(EXPAND2(EXPAND2(EXPAND2(__VA_ARGS__))))
#define EXPAND2(...) EXPAND3(EXPAND3(EXPAND3(EXPAND3(__VA_ARGS__))))
#define EXPAND3(...) __VA_ARGS__
// evaluates to nothing, but requires an additional preprocessor scan.
// this can be used to delay macro evaluations.
// examples:
// - CONCAT(a,b) => ab
// - CONCAT DELAY() (a,b) => a DELAY_IMPL_NOTHING () b
// - EXPAND(CONCAT DELAY() (a,b)) => ab
#define DELAY DELAY_IMPL_NOTHING
#define DELAY_IMPL_NOTHING()
// discards all arguments, evaluates to nothing
#define SWALLOW(...)
// appends an element to a tuple
// examples:
// - TUPLE_APPEND((a,b), c) => (a,b,c)
// - TUPLE_APPEND((), a) => (a)
#define TUPLE_APPEND(tuple, el) (TUPLE_APPEND_IMPL_UNPACK tuple el)
#define TUPLE_APPEND_IMPL_UNPACK(...) __VA_ARGS__ __VA_OPT__(,)
// if __VA_ARGS__ is empty then it expands to fn(args);
// otherwise it'll expand to FOR_EACH_COMBINATION_IMPL_RECURSE(fn, args, __VA_ARGS__)
#define FOR_EACH_COMBINATION_IMPL(fn, args, ...) \
IIF(HAS_VAARGS(__VA_ARGS__))( \
FOR_EACH_COMBINATION_IMPL_RECURSE, \
FOR_EACH_COMBINATION_IMPL_CALL \
)(fn, args __VA_OPT__(, __VA_ARGS__))
// evaluates the user-provided function-like macro fn with arguments args.
// example: FOR_EACH_IMPL_CALL(foo, (1,2)) => foo(1,2)
#define FOR_EACH_COMBINATION_IMPL_CALL(fn, args) \
fn args
// if tuple has at least 1 element it calls FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY;
// otherwise it stops recursion.
// examples:
// - FOR_EACH_COMBINATION_IMPL_RECURSE(fn, (), (a, b))
// => FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY(fn, (), (a,b))
// - FOR_EACH_COMBINATION_IMPL_RECURSE(fn, (), ())
// =>
#define FOR_EACH_COMBINATION_IMPL_RECURSE(fn, args, tuple, ...) \
IIF(HAS_VAARGS tuple)( \
FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY, \
SWALLOW \
) DELAY() ( \
fn, args, tuple __VA_OPT__(, __VA_ARGS__) \
)
// calls FOR_EACH_COMBINATION_IMPL twice;
// once with the first element of tuple appended to args,
// and a second time with the first element of tuple removed.
// examples:
// - FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY(fn, (), (a,b), (c,d))
// => FOR_EACH_COMBINATION_IMPL(fn, (a), (c,d))
// FOR_EACH_COMBINATION_IMPL(fn, (), (b), (c,d))
#define FOR_EACH_COMBINATION_IMPL_RECURSE_APPLY(fn, args, tuple, ...) \
FOR_EACH_COMBINATION_IMPL DELAY() ( \
fn, \
TUPLE_APPEND(args, VAARGS_HEAD tuple) \
__VA_OPT__(, __VA_ARGS__) \
) \
\
FOR_EACH_COMBINATION_IMPL DELAY() ( \
fn, \
args, \
(VAARGS_TAIL tuple) \
__VA_OPT__(, __VA_ARGS__) \
)
// takes a function-like macro (fn) and an arbitrary amount of tuples.
// the tuples can be of any size (only constrained by the number
// of expansions provided by the EXPAND macro)
// fn will be evaluated for each possible combination from the tuples.
// examples:
// - FOR_EACH_COMBINATION(foo, (a,b))
// => foo(a) foo(b)
// - FOR_EACH_COMBINATION(foo, (a,b), (1,2))
// => foo(a, 1) foo(a, 2) foo(b, 1) foo(b, 2)
#define FOR_EACH_COMBINATION(fn, ...) \
EXPAND( \
FOR_EACH_COMBINATION_IMPL( \
fn, \
() \
__VA_OPT__(, __VA_ARGS__) \
) \
)
// helper for getting a tuple of n consecutive int values, starting at 1.
// currently only works up to 5, but can be easily expanded by
// adding more INT_TUPLE_* macros.
// examples:
// - INT_TUPLE(0) => ()
// - INT_TUPLE(3) => (1,2,3)
// - INT_TUPLE(5) => (1,2,3,4,5)
#define INT_TUPLE(size) (CONCAT(INT_TUPLE_, size))
#define INT_TUPLE_0
#define INT_TUPLE_1 1
#define INT_TUPLE_2 INT_TUPLE_1, 2
#define INT_TUPLE_3 INT_TUPLE_2, 3
#define INT_TUPLE_4 INT_TUPLE_3, 4
#define INT_TUPLE_5 INT_TUPLE_4, 5
// EXAMPLES:
#define GEN(x) template void do_something<x>();
FOR_EACH_COMBINATION(
GEN,
(1,2,3)
)
#undef GEN
#define GEN(x, y, z) template bool match_expr<x,y,z>();
FOR_EACH_COMBINATION(
GEN,
INT_TUPLE(3),
(real, bool, index),
(0, 1)
)
#undef GEN
// all possible arrangements of 8 bits, in order.
// all8BitPatterns[1] == {0,0,0,0,0,0,0,1}
// all8BitPatterns[4] == {0,0,0,0,0,1,0,0}
// all8BitPatterns[9] == {0,0,0,0,1,0,0,1}
// etc..
#define GEN(b1,b2,b3,b4,b5,b6,b7,b8) {b1,b2,b3,b4,b5,b6,b7,b8},
int all8BitPatterns[256][8] = {
FOR_EACH_COMBINATION(
GEN,
(0, 1), (0, 1), (0, 1), (0, 1),
(0, 1), (0, 1), (0, 1), (0, 1)
)
};
#undef GEN
int main() {}