9

Mam wiele zagnieżdżonych pętli o małych rozmiarach I, J, ... znanych w czasie kompilacji, np.Rozwijane zagnieżdżanie za pomocą metaprogramowania

for(int i = 0; i < I; ++i) { 
    for(int j = 0; j < J; ++j) { 
     // ... 
     // do sth with (i,j,...) 
    } 
} 

trzeba odwijać pętli za pomocą wielkości I, J ... w taki sposób, że można użyć każdej kombinacji współrzędnych w kompilacji.

Aby wyjaśnić, rozważ następującą strukturę i weź 2 zagnieżdżone pętle o rozmiarach I = 2, J = 3.

template<int... I> 
struct C { 
    static void f() { 
      // do sth 
    } 
}; 

nie można korzystać z indeksów , j (podobnie jak wyżej), aby wskaźnik struktura C, ponieważ nie są znane w czasie kompilacji. Jednak to, co chciałbym wygenerować, to dokładnie to, co byłoby, gdyby pozwolono mi korzystać z indeksów, np.

C<0,0>::f(); 
C<0,1>::f(); 
C<0,2>::f(); 
C<1,0>::f(); 
C<1,1>::f(); 
C<1,2>::f(); 

Nie jestem szczególnie zainteresowany kolejnością generowania połączeń, o ile są produkowane wszystkie kombinacje. Mechanizm generowania powinien generalizować się do dowolnej liczby zagnieżdżonych pętli.

Odpowiedz

8

Można to zrobić, tworząc instancje szablonów w sposób przypominający drzewa, śledząc aktualnie odwiedzane węzły.

namespace detail{ 
    //This is used to store the visited nodes 
    template<int...> struct int_pack; 

    //Primary template 
    template<typename, int... I> 
    struct C; 

    //This is the leaf node 
    template<int... Is> 
    struct C<int_pack<Is...>> { 
     //The loop body goes here 
     static void f() { 
      std::cout << __PRETTY_FUNCTION__ << '\n'; 
     } 
    }; 

    //This is the recursive case 
    template <int I, int... Is, int... PIs> 
    struct C<int_pack<PIs...>, I,Is...> { 
     template <std::size_t... Idx> 
     static void f_help (std::index_sequence<Idx...>) { 
      //Store the current node in the pack 
      //and call `C::f` for each loop iteration 
      (void)std::initializer_list<int> { 
       (C<int_pack<PIs...,Idx>,Is...>::f(), 0)... 
      }; 
     } 

     //Use tag dispatching to generate the loop iterations 
     static void f() { 
      f_help(std::make_index_sequence<I>{}); 
     } 
    }; 
} 

//Helper alias 
template<int... Is> 
using C = detail::C<detail::int_pack<>, Is...>; 

Użycie jest bardzo proste:

C<2,3>::f(); 

Na Clang Drukuje:

static void detail::C<detail::int_pack<0, 0>>::f() [I = <>] 
static void detail::C<detail::int_pack<0, 1>>::f() [I = <>] 
static void detail::C<detail::int_pack<0, 2>>::f() [I = <>] 
static void detail::C<detail::int_pack<1, 0>>::f() [I = <>] 
static void detail::C<detail::int_pack<1, 1>>::f() [I = <>] 
static void detail::C<detail::int_pack<1, 2>>::f() [I = <>] 

Live Demo


Można uczynić to bardziej ogólne, aby można było wprowadzić ciało pętli do klasy przez lambdę, ale powyższe rozwiązanie powinno zrobić, jeśli chcesz zrobić to tylko raz i nie chcesz pobierać innych zależności, takich jak boost::hana. Oto możliwe wdrożenie wersji bardziej ogólny (można ją poprawić z doskonałej spedycji i podobne):

namespace detail{ 
    template<int...> struct int_pack; 

    template<typename, int... I> 
    struct C; 

    template<int... Is> 
    struct C<int_pack<Is...>> { 
     template <typename Func> 
     static void f(const Func& func) { 
      func(Is...); 
     } 
    }; 

    template <int I, int... Is, int... PIs> 
    struct C<int_pack<PIs...>, I,Is...> { 
     template <std::size_t... Idx, typename Func> 
     static void f_help (std::index_sequence<Idx...>, const Func& func) { 
      (void)std::initializer_list<int>{ (C<int_pack<PIs...,Idx>,Is...>::f(func), 0)... }; 
     } 

     template <typename Func> 
     static void f(const Func& func) { 
      f_help(std::make_index_sequence<I>{}, func); 
     } 
    }; 
} 

byłoby użyć to tak:

C<2,3>::f([](int i, int j){ 
    std::cout << "i " << i << " j " << j << '\n'; 
}); 

Live Demo


Oto szybka wersja, którą wykpiłam z boost::hana. Prawdopodobnie istnieją lepsze sposoby, aby to zrobić, ale powinno to dać ci wyobrażenie o tym, co można zrobić.

template <typename Func> 
void unroll (const Func& func) { 
    func(); 
} 

template <std::size_t I1, std::size_t... Is, typename Func> 
void unroll (const Func& func) { 
    hana::for_each(hana::range_c<std::size_t, 0, I1>, 
        [&](auto x) { 
         unroll<Is...>([x, &func] (auto... xs) { func(x,xs...); }); 
        }); 
} 
+0

Dziękuję za wzmiankę o 'boost: hana' w twojej odpowiedzi. Dowiedziałem się o tym więcej w ciągu ostatnich kilku dni. Jeśli dobrze znasz bibliotekę i masz trochę wolnego czasu, zastanawiałem się, czy możesz opublikować rozwiązanie, które z niej korzysta. –

+2

@TeodorNikolov Nie jestem zaznajomiony z biblioteką.Poleciłbym spróbować i poprosić o pomoc w projekcie [Gitter] (https://gitter.im/boostorg/hana), jeśli utkniesz. Napisałem tam raz lub dwa razy, a twórcy są bardzo przyjaźni. – TartanLlama

+1

@TeodorNikolov Szybko opisałem, jak można to zrobić w 'boost :: hana', ale prawdopodobnie najlepiej zbadaj to sam. – TartanLlama

Powiązane problemy