Someone who is better at this can improve my answer.
The bottom line of my solution is that you declare sizes N with start and end.
It repeats the measurements of N-1 with the same beginning and end.
When he reaches the 1st dimension, he will actually begin to increase the beginning, causing the transferred function.
He will always try to pass several arguments that are identical to the number of dimensions (their indices).
So, the call is:
meta_for<2, 0, 2>::loop( [](size_t i, size_t j) { std::cout << i << " " << j << std::endl; });
The result will look like this:
0 0
0 1
ten
eleven
Here's the meta_for structure using the helper, iterate :
template<size_t D, size_t B, size_t E> struct meta_for { template<typename Func> static void loop(Func&& func) { iterate<D, B, B, E>::apply(std::forward<Func>(func)); } };
And helpers:
Detailed explanation
This solution, like any other using variational patterns, depends on recursion.
I wanted to express recursion on the outer loop, so I started with the base case; end of cycle. This is the case when the beginning coincides with the end:
template<int Dim, size_t B, size_t E> struct iterate<Dim, E, B, E> { };
Note that this is a specialization for <Dim, E, B, E> . The second position indicates the current index of the external circuit, and the last position indicates that the index iterates to (but not including). So, in this case, the current index coincides with the last, indicating that we have finished the cycle (and, therefore, the function "do nothing").
The recursive case for the outer loop includes a scenario in which the loop index is smaller than the index for iteration. In template conditions, the second position is less than the fourth position:
template<int Dim, size_t S, size_t B, size_t E> struct iterate {}
Please note that this is NOT a specialization.
The logic of this function is that the outer loop must signal the inner loop to start execution from the beginning, and then the outer loop continues and starts the process again for the inner loops:
iterate<Dim-1, B, B, E>::apply (func, a..., S); iterate<Dim, S+1, B, E>::apply (func, a...);
Notice that in the first line, the second argument of the pattern is again B , which means you have to start from the beginning again. This is necessary because another recursive case on the second line increases S (increasing the index of the outer loop).
All the time, we also accumulate arguments for moving to a function:
::apply(func, a..., S)
passes the on function along with the indices of higher measurement cycles, and then adds the current cycle index ( S ). a Here is a variation pattern.
Inner loop
When I say โinner loop,โ I mean the innermost loop. This loop should simply increase until the start index reaches the end index and tries to recurs to any lower size. In our case, this is when our Dim (Dimension) parameter is 1:
template<size_t S, size_t B, size_t E> struct iterate<1, S, B, E> {};
At this point, we finally want to call our function passed along with all the arguments that we have accumulated so far (indexes of outer loops) PLUS, the index of the innermost loop:
func(a..., B);
And then recurse (index index)
iterate<1, S, B+1, E>::apply(func, a...);
The basic example here is when the innermost loop index matches the end index (AND the dimension is 1):
template<size_t S, size_t E> struct iterate<1, S, E, E> {};
Therefore, there is "do nothing"; no work should be done because the loop ends.
Finally, I turned on one last specialization to catch a user error where they didnโt indicate any dimensions:
template<size_t S, size_t B, size_t E> struct iterate<0, S, B, E>
Uses static_assert to always fail because sizeof(size_t) not zero:
static_assert(sizeof(S) == 0, "Need more than 0 dimensions!");
Conclusion
This is a meta-program template for a specific use case. Where we essentially generate N nested loops that have the same start and end indices And we want to pass these indices to a function. We could do a little more work to make it so that the iterate structure could stand on its own, without making the assumption that the start and end indices of the outer loop coincide with the inner loops.
My favorite application of this code is that we can use it to create an N-dimensional counter. For example, a binary counter for N-bits (found in a live demo).