2013-04-01 54 views
7

moim celem jest zrobić coś tak, że na przykład,szablony o zmiennej liczbie argumentów: wytwarzanie krotki par sąsiednich elementów

pairs<1,2,3,4>() 

ma typ powrócić

std::tuple<some_other_type<1,2>, some_other_type<2,3>, some_other_type<3,4>> 

Zastanawiam się, czy jest to w ogóle możliwe z metaprogramowaniem szablonów C++ i jak można to osiągnąć. Aby rzeczywiście uzyskać tę wartość, wydaje się, że mogę użyć tuple_cat do rekursywnego łączenia się z danymi wyjściowymi, ale trudno mi jest wyrazić typ zwracany, ponieważ sam jest wariantowy i faktycznie jest funkcją liczby parametrów szablonu. Komplikując sytuację, gdybym przeszedł na ścieżkę tuple_cat, wydaje mi się, że musiałbym przeciążać funkcję, aby pobrać krotkę, a konkatenacja miałaby miejsce w czasie wykonywania, a nie podczas kompilacji. Czy jestem tutaj na dzikim gonitwie?

+0

+1 Prawdopodobnie jest osiągalne przy użyciu klasy ... –

Odpowiedz

12

Oto sposób robienia tego. Biorąc pod uwagę, klasa szablon some_other_type:

template<int I, int J> 
struct some_other_type { }; 

A biorąc pod uwagę niektóre maszyny ukryty w detail nazw:

namespace detail 
{ 
    template<int... Is> 
    struct pairs { }; 

    template<int I, int J> 
    struct pairs<I, J> 
    { 
     using type = std::tuple<some_other_type<I, J>>; 
    }; 

    template<int I, int J, int... Is> 
    struct pairs<I, J, Is...> 
    { 
     using type = decltype(std::tuple_cat(
       std::tuple<some_other_type<I, J>>(), 
       typename pairs<J, Is...>::type())); 
    }; 
} 

Można zapewnić prostą funkcję, która tworzy wystąpienie szablonu klasy pomocnika:

template<int... Is> 
typename detail::pairs<Is...>::type pairs() 
{ 
    return typename detail::pairs<Is...>::type(); 
} 

A oto jak z niego korzystać (i przypadek testowy):

#include <type_traits> 

int main() 
{ 
    auto p = pairs<1, 2, 3, 4>(); 

    // Won't fire! 
    static_assert(
     std::is_same< 
      decltype(p), 
      std::tuple< 
       some_other_type<1,2>, 
       some_other_type<2,3>, 
       some_other_type<3,4>> 
      >::value, 
      "Error!"); 
} 

Wreszcie, tutaj jest live example.


POPRAWA: (dlaczego pisanie <1, 2, 3, 4> kiedy można napisać <1, 5>)?

Możliwe jest również rozszerzenie powyższego rozwiązania, aby nie było wymagane ręczne zapisywanie każdej liczby między minimum a maksimum jako argumentu szablonu o wartości pairs(). Ze względu na dodatkowy sprzęt poniżej znowu ukryty w detail nazw:

namespace detail 
{ 
    template <int... Is> 
    struct index_list { }; 

    template <int MIN, int N, int... Is> 
    struct range_builder; 

    template <int MIN, int... Is> 
    struct range_builder<MIN, MIN, Is...> 
    { 
     typedef index_list<Is...> type; 
    }; 

    template <int MIN, int N, int... Is> 
    struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...> 
    { }; 

    // Meta-function that returns a [MIN, MAX) index range 
    template<int MIN, int MAX> 
    using index_range = typename range_builder<MIN, MAX>::type; 

    template<int... Is> 
    auto pairs_range(index_list<Is...>) -> decltype(::pairs<Is...>()) 
    { 
     return ::pairs<Is...>(); 
    } 
} 

Możliwe jest zdefiniowanie funkcji pomocnika pairs_range() która przyjmuje 2 argumenty szablonów określających zakres [begin, end) - gdzie end nie wliczono w stylu Biblioteka standardowa:

template<int I, int J> 
auto pairs_range() -> decltype(pairs_range(detail::index_range<I, J>())) 
{ 
    return pairs_range(detail::index_range<I, J>()); 
} 

a to w jaki sposób go używać (w tym przypadku testowego):

int main() 
{ 
    // Won't fire! 
    static_assert(
     std::is_same< 
      decltype(pairs_range<1, 5>()), 
      decltype(pairs<1, 2, 3, 4>()) 
      >::value, 
      "Error!"); 
} 

I znowu tutaj jest live example.

4

Oto moja wersja to (live here), 100% czasu kompilacji, wracając nową listę parametrów jako typ (nie powrotnej funkcji):

Najpierw zdefiniować nasze struktury Wynik:

template<int a, int b> 
struct tpair 
{ 
}; 

template<typename... p> 
struct final_ 
{ 
}; 

Kluczowym punktem jest dołączanie pakietów parametrów. Oto struktura, która będzie wykonać zadanie:

template<typename a, typename b> 
struct concat 
{ 
}; 

template<typename a, typename... b> 
struct concat<a, final<b...>> 
{ 
    typedef final_<a,b...> type; 
}; 

Teraz struct używane do 'pairize' listy. Normalnie nie powiedzie się z nieparzystej liczby wartości:

template<int a, int b, int... values> 
struct pairize 
{ 
    // Choose one of the following versions: 
    // First version: only non-overlapping pairs : (1,2) (3,4) ... 
    typedef typename concat<tpair<a,b>, typename pairize<values...>::type>::type type; 
    // Second version: overlapping pairs : (1,2) (2,3) (3,4)... 
    typedef typename concat<tpair<a,b>, typename pairize<b,values...>::type>::type type; 
}; 

template<int a, int b> 
struct pairize<a,b> 
{ 
    typedef final_<tpair<a,b>> type; 
}; 

W żywej przykład jest tam również kod wpisywanie nazwy wszystkich typów w opakowaniu parametr do konsoli z demangling, jako test (był zabawniejszy w użyciu niż sztuczka niekompletna).

+0

pamiętać, że brakuje każdą inną parę (on też chce 2-3 i 4-5). –

+0

Masz rację, zbyt szybko przeczytałem pytanie. Daj mi czas, aby to naprawić;) – Synxis

+0

Myślę, że jest to poprawka 2-znakowa ;-) –

3

A teraz spróbujmy go indices i bez rekursji (z wyjątkiem, oczywiście, dla indeksów):

#include <tuple> 

template< std::size_t... Ns > 
struct indices 
{ 
    typedef indices< Ns..., sizeof...(Ns) > next; 
}; 

template< std::size_t N > 
struct make_indices 
{ 
    typedef typename make_indices< N - 1 >::type::next type; 
}; 

template<> 
struct make_indices<0> 
{ 
    typedef indices<> type; 
}; 

template< std::size_t, std::size_t > 
struct sometype {}; 

template< typename, typename, typename > 
struct make_pairs; 

template< std::size_t... Ns, std::size_t... Ms, std::size_t... Is > 
struct make_pairs< indices<Ns...>, indices<Ms...>, indices<Is...> > 
{ 
    using type = decltype(std::tuple_cat(
    std::declval< typename std::conditional< Is % 2 == 1, 
              std::tuple< sometype< Ns, Ms > >, 
              std::tuple<> >::type >()... 
)); 
}; 

template< std::size_t... Ns > 
using pairs = typename make_pairs< indices< 0, Ns... >, indices< Ns..., 0 >, 
       typename make_indices< sizeof...(Ns) + 1 >::type >::type; 

int main() 
{ 
    static_assert(std::is_same< pairs<1,2,4,3,5,9>, 
    std::tuple< sometype<1,2>, sometype<4,3>, sometype<5,9> > >::value, "Oops"); 
} 

(OK, ja oszukiwałem trochę: std::tuple_cat może być rekurencyjna sama;)


Aktualizacja: OK, powinienem był przeczytać to pytanie dokładniej. Oto wersja, która produkuje pożądanego rezultatu (indices/make_indices jak wyżej):

template< std::size_t, std::size_t > 
struct sometype {}; 

template< typename, typename, typename > 
struct make_pairs; 

template< std::size_t... Ns, std::size_t... Ms, std::size_t... Is > 
struct make_pairs< indices<Ns...>, indices<Ms...>, indices<Is...> > 
{ 
    using type = decltype(std::tuple_cat(
    std::declval< typename std::conditional< Is != 0 && Is != sizeof...(Is) - 1, 
              std::tuple< sometype< Ns, Ms > >, 
              std::tuple<> >::type >()... 
)); 
}; 

template< std::size_t... Ns > 
using pairs = typename make_pairs< indices< 0, Ns... >, indices< Ns..., 0 >, 
       typename make_indices< sizeof...(Ns) + 1 >::type >::type; 

int main() 
{ 
    static_assert(std::is_same< pairs<1,2,3,4>, 
    std::tuple< sometype<1,2>, sometype<2,3>, sometype<3,4> > >::value, "Oops"); 
} 
+0

+1. Po dwudziestu minutach patrzenia na to, prawie uda mi się zobaczyć, jak i dlaczego działa. Ale nigdy nie byłbym w stanie sam tego rozgryźć. Bardzo interesująca technika, muszę się tego nauczyć. –

+0

@AndyProwl To może wyglądać dziwnie, ale są przypadki, w których konieczne jest uniknięcie rekursji. Zazwyczaj nie ma to znaczenia, o ile tylko obliczasz typy, ale gdy rekursja wymaga generowania kodu, różnica jest ogromna! Właśnie dlatego lubię tego rodzaju pytania i dlatego używam ich do treningu, nawet jeśli trwa to nieco dłużej niż prosta wersja rekursywna. –

+0

Rzeczywiście, to bardzo interesujące. Powinienem ćwiczyć to bardziej. Zapamiętałem tę technikę, ponieważ użyłeś jej już [w innej odpowiedzi] (http://stackoverflow.com/questions/15411022/how-do-i-replace-a-tuple-element-at-compile-time/15412010#15412010), ale nigdy nie przyszło mi do głowy, że można go tutaj wykorzystać. I po przemyśleniu trochę tego, wydaje się, że jest on praktycznie wszędzie. Jeśli masz więcej przykładów problemów, w których ten idiom przynosi "naturalne" rozwiązanie, możesz je przynieść, będę wdzięczny;) –

Powiązane problemy