I have an integer N which I know at compile time. I also have an std::array holding integers describing the shape of an N-dimensional array. I want to generate nested loops, as described bellow, at compile time, using metaprogramming techniques.
constexpr int N {4}; constexpr std::array<int, N> shape {{1,3,5,2}}; auto f = [/* accept object which uses coords */] (auto... coords) { // do sth with coords }; // This is what I want to generate. for(int i = 0; i < shape[0]; i++) { for(int j = 0; j < shape[1]; j++) { for(int k = 0; k < shape[2]; k++) { for(int l = 0; l < shape[3]; l++) { f(i,j,k,l) // object is modified via the lambda function. } } } }
Note the parameter N is known at compile time but might change unpredictably between compilations, hence I can't hard code the loops as above. Ideally the loop generation mechanism will provide an interface which accepts the lambda function, generates the loops and calls the function producing the equivalent code as above. I am aware that one can write an equivalent loop at runtime with a single while loop and an array of indices, and there are answers to this question already. I am, however, not interested in this solution. I am also not interested in solutions involving preprocessor magic.
A nested loop is a loop within a loop, an inner loop within the body of an outer one. How this works is that the first pass of the outer loop triggers the inner loop, which executes to completion. Then the second pass of the outer loop triggers the inner loop again. This repeats until the outer loop finishes.
The outer loop's runtime (by itself) is O(n), and the inner loop's runtime is O(n-i). So the loop's time would be (n)(n-i), and when you throw away the constant i, the runtime would be O(n^2).
Nested Loops The placing of one loop inside the body of another loop is called nesting. When you "nest" two loops, the outer loop takes control of the number of complete repetitions of the inner loop. While all types of loops may be nested, the most commonly nested loops are for loops.
Something like this (NOTE: I take the "shape" as a variadic template argument set..)
#include <iostream> template <int I, int ...N> struct Looper{ template <typename F, typename ...X> constexpr void operator()(F& f, X... x) { for (int i = 0; i < I; ++i) { Looper<N...>()(f, x..., i); } } }; template <int I> struct Looper<I>{ template <typename F, typename ...X> constexpr void operator()(F& f, X... x) { for (int i = 0; i < I; ++i) { f(x..., i); } } }; int main() { int v = 0; auto f = [&](int i, int j, int k, int l) { v += i + j + k + l; }; Looper<1, 3, 5, 2>()(f); auto g = [&](int i) { v += i; }; Looper<5>()(g); std::cout << v << std::endl; }
Assuming you don't want total loop unrolling, just generation of i
, j
, k
etc. argument tuples for f
:
#include <stdio.h> #include <utility> // std::integer_sequence template< int dim > constexpr auto item_size_at() -> int { return ::shape[dim + 1]*item_size_at<dim + 1>(); } template<> constexpr auto item_size_at<::N-1>() -> int { return 1; } template< size_t... dim > void call_f( int i, std::index_sequence<dim...> ) { f( (i/item_size_at<dim>() % ::shape[dim])... ); } auto main() -> int { int const n_items = ::shape[0]*item_size_at<0>(); for( int i = 0; i < n_items; ++i ) { call_f( i, std::make_index_sequence<::N>() ); } }
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