2013-04-01 8 views
7

Il mio obiettivo è di fare qualcosa in modo che, per esempio,modelli variadic: la produzione di una tupla di coppie di elementi adiacenti

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

Ha tipo di ritorno

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

Mi chiedo se questo è ancora possibile con metaprogrammazione del modello C++ e come potrebbe essere realizzato. Per produrre effettivamente il valore, sembra che io possa usare tuple_cat per concatenare ricorsivamente all'output, ma trovo difficile esprimere il tipo restituito, poiché è esso stesso variabile ed efficacemente una funzione del numero di parametri del template. Complicando la situazione, se avessi percorso la tuple_cat sembra che avrei dovuto sovraccaricare la funzione per prendere una tupla su cui concatenare, e la concatenazione sarebbe avvenuta in fase di runtime, non in fase di compilazione. Sono su una caccia all'oca selvaggia qui?

+0

+1 Questo è probabilmente realizzabile utilizzando una classe ... –

risposta

12

Ecco un modo per farlo. Dato il tuo modello di classe some_other_type:

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

E dato alcuni macchinari nascosta nel detail namespace:

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())); 
    }; 
} 

si potrebbe fornire una semplice funzione che crea un'istanza del modello di classe helper:

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

Ed ecco come lo useresti (e un caso di test):

#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!"); 
} 

Infine, ecco uno live example.


MIGLIORAMENTO: (perché la scrittura <1, 2, 3, 4> in cui si poteva scrivere <1, 5>)?

È anche possibile estendere la soluzione di cui sopra in modo che non sia necessario scrivere manualmente ogni numero tra il minimo e il massimo come argomento modello di pairs(). Dato il meccanismo aggiuntivo di seguito, di nuovo nascosto in uno spazio dei nomi detail:

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...>(); 
    } 
} 

E 'possibile definire una funzione di supporto pairs_range() che accetta 2 argomenti di template che definiscono la gamma [begin, end) - dove end non è incluso, nello stile del standard Library:

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

E questo è come si potrebbe usarlo (compreso un caso di test):

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

E ancora una volta, ecco uno live example.

4

Qui è la mia versione di esso (live here), 100% in fase di compilazione, restituendo il nuovo elenco di parametri come un tipo (non un ritorno funzione):

In primo luogo, definiamo le nostre strutture di risultato:

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

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

Il punto chiave è concat i pacchetti di parametri. Ecco l'struct che farà il lavoro:

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

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

Ora, la struct utilizzato per 'pairize' la vostra lista. Normalmente fallirà con un numero dispari di valori:

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

Nell'esempio in diretta c'è anche un codice di emettere il nome di tutti i tipi in un parametro pack per la console, con demangling, come test (era più divertente da usare rispetto al trucco di tipo incompleto).

+0

noti che si sta perdendo ogni altra coppia (lui vuole anche 2-3 e 4-5). –

+0

Hai ragione, ho letto la domanda troppo velocemente. Dammi il tempo di risolvere il problema;) – Synxis

+0

Penso che sia una correzione a 2 caratteri ;-) –

3

Ed ora, proviamo con indices e senza ricorsione (ad eccezione, naturalmente, per gli indici):

#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, ho barato un po ': std::tuple_cat potrebbe essere ricorsiva stesso;)


Aggiornamento: OK, avrei dovuto leggere la domanda con più attenzione. Ecco la versione che produce il risultato desiderato (indices/make_indices come sopra):

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. Dopo venti minuti di osservazione di questo, riesco quasi a vedere come e perché funziona. Ma non sarei mai stato in grado di capirlo da solo. Tecnica molto interessante, ho bisogno di imparare questo bene. –

+0

@AndyProwl Potrebbe sembrare strano, ma ci sono casi in cui è fondamentale evitare la ricorsione. Di solito non è importante finché si calcola solo i tipi, ma quando la ricorsione comporta la generazione del codice, la differenza è enorme! Questo è il motivo per cui mi piace questo tipo di domande e perché li uso per allenarmi, anche se richiede un po 'più di tempo rispetto alla semplice versione ricorsiva. –

+0

In effetti, è molto interessante. Dovrei esercitarmi di più. Mi sono ricordato di questa tecnica perché l'hai già usata [in un'altra risposta] (http://stackoverflow.com/questions/15411022/how-do-i-replace-a-tuple-element-at-compile-time/15412010#15412010), ma non mi sarebbe mai venuto in mente che potesse essere usato qui. E dopo aver pensato un po ', sembra che sia utilizzabile praticamente ovunque. Se avete altri esempi di problemi in cui questo idioma porta una soluzione "naturale", sentitevi liberi di portarli su, sarò grato;) –

Problemi correlati