3

In my project, I want to have a bunch of explicit instantiations of my templated functions to reduce build time. Now I have a lot of functions, which can have different templates. For this reason (and in case I want to have more of them) I do not want to type them manually, but have them generated by the preprocessor.

An example of what I want to have generated:

template bool match_any<x>();
template bool match_any<y<x>>();
template bool match_expr<x,y,z>();

whereas x is an integer between 1 and a defined max_dim, y can be one of three values: real, bool, index and z is either 0 or 1.

I want to generate now any possible combination of those three functions with the parameters (e.g. template bool match_any<0>(); template bool match_any<1>(); template bool match_any<2>(); ...), but not only specifically those, since I have ~100 of those functions with a similar structure.

Through Using macros to define forward declarations I have figured out how to do a repetitive pattern:

#define REP_INT_1(f) f(1)
#define REP_INT_2(f) REP_INT_1(f) f(2)
#define REP_INT_3(f) REP_INT_2(f) f(3)
#define REP_INT_4(f) REP_INT_3(f) f(4)

#define REP_INT(n,gen) REP_INT_(n,gen)
#define REP_INT_(n,gen) REP_INT_##n(gen)

which I can then use like

#define GEN(x) template bool match_any<x>();
REP_INT(3, GEN)
#undef GEN

Of course it is simple to repeat this pattern e.g. for strings.

But my problem is now, that because of the nature of the pattern (as I pass GEN as a 'function') I cannot of two arguments for GEN. Sure, I can altered the repetition pattern so it works also for two arguments, but then I would have to make a new pattern like this for any number of arguments and also for every order of them, which ultimatly kind of defeats the purpose, because I will have a big block for every function - then I can just write it manually.

Does anyone have an idea of to accomplish this with either a different way to 'loop' the possible arguments or how to extend my existing methods so it works for multiple arguments?

Jason
  • 36,170
  • 5
  • 26
  • 60
wittn
  • 298
  • 5
  • 16

2 Answers2

1

You can add to your macros like so to achieve a somewhat maintainable list:

#define GEN_X(f) REP_INT(3, f)
#define GEN_Y(f, x) f(x, real) f(x, bool) f(x, index)
#define GEN_Z(f, x, y) f(x, y, 0) f(x, y, 1)

// GEN_F3 = generate functions with at least 3 arguments
#define GEN_F3(x, y, z) template bool match_any<x, y, z>();
#define GEN_F2(x, y) template bool match_any<y<x>>(); GEN_Z(GEN_F3, x, y)
#define GEN_F1(x) template bool match_any<x>(); GEN_Y(GEN_F2, x)
GEN_X(GEN_F1)

demo

We separate our variable generators which we can then chain together to get all our permutations: Your macro generates the x's, which is piped into GEN_F1, which handles the single-argument case and pipes those x values to the y-generator, and so on.

Be careful that although we've made the source code linearly maintainable, we can't avoid the exponential increase to the executable size.


To address your extended question, if you wanted to be able to handle any permutation of numbers for two arguments, say for x={1,2,3}, y={1,2,3,4}, intuitively you may want to adjust like so:

#define GEN_X(f) REP_INT(3, f)
#define GEN_Y(f, x) REP_INT(4, f)

This almost works, but macro expansion prevents using the same REP_INT_* macros again within the one recursive expansion (at least in the way that I've done it).

One work-around is to have two REP_INT lists (and at least one of them needs to handle variadic inputs, appending the number to the end). The macros can be identical, except that the names need to differ to allow the expansion to continue.

We can then solve the extended problem like so:

#define GEN_X(f) REP1_INT_3(f)
#define GEN_Y(f, x) REP2_INT_4(f, x)

#define GEN_F2(x, y) template bool match_any<x, y>();
#define GEN_F1(x) GEN_Y(GEN_F2, x)
GEN_X(GEN_F1)

demo

Elliott
  • 2,603
  • 2
  • 18
  • 35
  • This is very nice and it works for the test cases. I unfortunaly did not include this in my example, but what am I ought to do if two of the arguments are numbers? (e.g. If I want to generate match_any<1,1>, match_any<2,1>, match_any<3,1>, match_any<1,2>.. etc.). – wittn Oct 17 '22 at 14:50
  • 1
    @wittn, well that's a different question. I'm not sure it's possible without a second `REP_INT` series. I'll have a think about it. – Elliott Oct 18 '22 at 02:43
  • Thanks a lot for thinking about it! Also, by 'a second `REP_INT` series, do you mean a second one in a similar style / length as the first one or would I need one for every combination (because the latter would be extremly long, the first one would be fine though). – wittn Oct 18 '22 at 05:55
  • 1
    @wittn, no problem. I've just edited my answer to address your comment, but here's an example of how you can have an ***identical*** macro series except for the name: [coliru](http://coliru.stacked-crooked.com/a/2a758dc2d33a4ed6) – Elliott Oct 18 '22 at 06:20
1

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:

  • If there are no more tuples to pick elements from (__VA_ARGS__ is empty), we just call fn(args)
    FOR_EACH_COMBINATION_IMPL(fn, (a, 1)) => fn(a, 1)
    
  • If there are still tuples to pick elements from (at least one __VA_ARGS__ argument is present) we call FOR_EACH_COMBINATION_IMPL_RECURSE, which checks if the first tuple has any options left to pick from:
    • If there are options left to pick from it'll result in 2 additional calls to FOR_EACH_COMBINATION_IMPL; one to handle the branch where the first element is picked, and one to handle all the remaining elements:
      FOR_EACH_COMBINATION_IMPL(fn, (), (a, b), (1, 2))
      =>
      FOR_EACH_COMBINATION_IMPL(fn, (a), (1, 2))
      FOR_EACH_COMBINATION_IMPL(fn, (), (b), (1, 2))
      
    • If there are no options left it evaluates to nothing (stops recursion):
      FOR_EACH_COMBINATION_IMPL(fn, (), (), (1, 2)) => /* nothing */
      

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() {}
Turtlefight
  • 9,420
  • 2
  • 23
  • 40