Подтвердить что ты не робот

Как создать вложенные циклы во время компиляции

У меня есть целое число N, которое я знаю во время компиляции. У меня также есть целые числа std:: array, описывающие форму N -мерного массива. Я хочу генерировать вложенные циклы, как описано ниже, во время компиляции, используя методы метапрограммирования.

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.
                }
          }
     }
}

Обратите внимание, что параметр N известен во время компиляции, но может непредсказуемо изменяться между компиляциями, поэтому я не могу жестко закодировать петли, как указано выше. В идеале механизм генерации цикла обеспечит интерфейс, который принимает функцию лямбда, генерирует петли и вызывает функцию, генерирующую эквивалентный код, как указано выше. Я знаю, что можно написать эквивалентный цикл во время выполнения с одним циклом while и массивом индексов, и есть ответы на этот вопрос. Я, однако, не заинтересован в этом решении. Меня также не интересуют решения, связанные с маской препроцессора.

4b9b3361

Ответ 1

Что-то вроде этого (ПРИМЕЧАНИЕ. Я принимаю "форму" как набор аргументов вариационного шаблона.)

#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;
}

Ответ 2

Предполагая, что вы не хотите разворачивать полный цикл, просто генерируете кортежи i, j, k и т.д. для 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>() );
    }
}

Ответ 3

Я предполагаю, что это именно то, о чем вы просили:

#include <array>
#include <iostream>

constexpr int N{4};
constexpr std::array<int, N> shape {{1,3,5,2}};

// Diagnositcs

template<typename V, typename ...Vals>
struct TPrintf {
        constexpr static void call(V v, Vals ...vals) {
                std::cout << v << " ";
                TPrintf<Vals...>::call(vals...);
        }
};

template<typename V>
struct TPrintf<V> {
        constexpr static void call(V v) {
                std::cout << v << std::endl;
        }
};


template<typename ...Vals>
constexpr void t_printf(Vals ...vals) {
        TPrintf<Vals...>::call(vals...);
}

// Unroll

template<int CtIdx, typename F>
struct NestedLoops {
        template<typename ...RtIdx>
        constexpr static void call(const F& f, RtIdx ...idx) {
                for(int i = 0; i < shape[CtIdx]; ++i) {
                        NestedLoops<CtIdx + 1, F>::call(f, idx..., i);
                }
        }
};

template<typename F>
struct NestedLoops<N-1, F> {
        template<typename ...RtIdx>
        constexpr static void call(const F& f, RtIdx ...idx) {
                for(int i = 0; i < shape[N-1]; ++i) {
                        f(idx..., i);
                }
        }
};

template<typename F>
void nested_loops(const F& f) {
        NestedLoops<0, F>::call(f);
}

int main()
{
        auto lf = [](int i, int j, int k, int l) {
                t_printf(i,j,k,l);
        };

        nested_loops(lf);
        return 0;
}

Ответ 4

Другой вариант того же:

template <size_t shape_index, size_t shape_size>
struct Looper
{
    template <typename Functor>
    void operator()(const std::array<int, shape_size>& shape, Functor functor)
    {
        for (int index = 0; index < shape[shape_index]; ++index)
        {
            Looper<shape_index + 1, shape_size>()
                (
                    shape,
                    [index, &functor](auto... tail){ functor(index, tail...); }
                );
        }
    }
};

template <size_t shape_size>
struct Looper<shape_size, shape_size>
{
    template <typename Functor>
    void operator()(const std::array<int, shape_size>&, Functor functor)
    {
        functor();
    }
};

template <size_t shape_size, typename Functor>
void loop(const std::array<int, shape_size>& shape, Functor functor)
{
    Looper<0, shape_size>()(shape, functor);
}

Пример использования:

constexpr size_t N {4};

constexpr std::array<int, N> shape {{1,3,5,2}};

void f(int i, int j, int k, int l)
{
    std::cout
        << std::setw(5) << i
        << std::setw(5) << j
        << std::setw(5) << k
        << std::setw(5) << l
        << std::endl;
}

// ...

loop(shape, f);

Live demo