2013-04-09 10 views
30

Ho una funzione modello con varargs argomenti template, come questoCome invertire l'ordine degli argomenti di una funzione di modello variadic?

template<typename Args...> 
void ascendingPrint(Args... args) { /* ... */ } 

E voglio scrivere

template<typename Args...> 
void descendingPrint(Args... args) { 
    /* implementation using ascendingPrint()? */ 
} 

Come faccio invertire l'ordine del parametro-pack args prima di passarlo, vale a dire in pseudo-codice:

template<typename Args...> 
void descendingPrint(Args... args) { 
    ascendingPrint(reverse(args)); 
} 
+0

[Questo] (http://stackoverflow.com/questions/8773426/iterating-variadic-template-arguments-in-reverse-order) potrebbe essere utile! – Carsten

+0

@Aschratt L'ho visto, penso che il titolo della domanda sia fuorviante. – towi

+1

L'espansione in una tupla e la generazione di indici in ordine inverso sarebbe un modo relativamente semplice, sebbene ciò induca un sovraccarico per la costruzione della tupla. – Xeo

risposta

12

Ecco una ricorsiva implementazione di una specializzata revert<>:

// forward decl 
template<class ...Tn> 
struct revert; 

// recursion anchor 
template<> 
struct revert<> 
{ 
    template<class ...Un> 
    static void apply(Un const&... un) 
    { 
     ascendingPrint(un...); 
    } 
}; 

// recursion 
template<class T, class ...Tn> 
struct revert<T, Tn...> 
{ 
    template<class ...Un> 
    static void apply(T const& t, Tn const&... tn, Un const&... un) 
    { 
     // bubble 1st parameter backwards 
     revert<Tn...>::apply(tn..., t, un...); 
    } 
}; 

// using recursive function 
template<class A, class ...An> 
void descendingPrint(A const& a, An const&... an) 
{ 
    revert<An...>::apply(an..., a); 
} 

Funziona con gcc-4,6/7/8 e clang ed è probabilmente lo stand ard compliant - l'unica parte difficile è la chiamata di revert<Tn...>::apply(tn..., t, un...).

Ha inconvenienti anche se (come ricorsione ha spesso), che genera un sacco di template-istanze della funzione target (codice troppo grosso) e non usare perfetta inoltro, che può essere un problema (ma forse potrebbe essere migliorato per usarlo).

+0

Bella, e sembra una traduzione letterale di Haskell in TMP/smile /. out Sembra abbastanza semplice. Perché dici "funziona con gcc 4.8"? Non pensi che sia Std? Non riesco a capire perché non lo fosse. Sei preoccupato per i molteplici * pacchetti di parametri * di 'apply'? Per quanto mi ricordi _function-templates_ ** può ** avere più di un * parameter-pack * - solo _class-templates_ non può. Foglie 'apply (tn ..., t, un ...)' e la domanda se il compilatore può srotolare correttamente tutti gli argomenti. "Provato con l'esempio" (gcc) sembra che dovrebbe, ma anche lo standard lo dice? – towi

+2

@towi Grazie! Penso che questo sia std C++, ma gcc 4.8 è stato l'unico compilatore che ho testato. [Here] (http://liveworkspace.org/code/2Sxk4k$0) è una versione leggermente modificata. Funziona con gcc 4.6/7/8 e clang, ma non con il compilatore Intel. – Alex

+2

Ho accettato la tua risposta in favore di quelle non ricorsive perché mi piace la ricorsione da solo e questa versione è la più facile da capire per me. Ma è stata una scelta difficile: invito i lettori a ** a leggere le altre risposte too **, di [AndyPowl] (http://stackoverflow.com/a/15904742/472245) e [Xeo] (http://stackoverflow.com/a/15908420/472245), (e forse i commenti) - - possono avere benefici di cui hai bisogno. E [M-M] (http://stackoverflow.com/a/15904836/472245) ha anche un punto, se non hai bisogno di una soluzione generale. – towi

23

approccio generale e l'uso di


L'approccio overal consiste nel confezionamento degli argomenti in un std::tuple di riferimenti, sfruttando la perfetta inoltro macchinari di std::forward_as_tuple().

Ciò significa che, in fase di esecuzione, è necessario sostenere un sovraccarico molto piccolo e nessuna operazione di copia/spostamento non necessaria. Inoltre, il framework non utilizza la ricorsione (a parte la ricorsione in fase di compilazione, che è inevitabile per la generazione di indici), quindi nessun rischio di sovraccarico in fase di esecuzione anche nel caso in cui il compilatore non riuscisse a incorporare le chiamate ricorsive è improbabile in ogni caso, quindi questo è più di un argomento accademico).

Inoltre, questa soluzione è generale, nel senso che si può usare come una libreria di testa solo per richiamare le funzioni con argomenti invertiti e con il minimo sforzo: descending_print() dovrebbe essere solo una minima involucro leggero intorno ascending_print().

Ecco come dovrebbe apparire come:

MAKE_REVERT_CALLABLE(ascending_print) 

template<typename... Args> 
void descending_print(Args&&... args) 
{ 
    revert_call(REVERT_ADAPTER(ascending_print), std::forward<Args>(args)...); 
} 

Quella che segue è una presentazione della realizzazione.


Primo passo: ritornando una sequenza tipo


Ecco un modo semplice per ripristinare una sequenza tipo:

#include <tuple> 
#include <type_traits> 

template<typename, typename> 
struct append_to_type_seq { }; 

template<typename T, typename... Ts> 
struct append_to_type_seq<T, std::tuple<Ts...>> 
{ 
    using type = std::tuple<Ts..., T>; 
}; 

template<typename... Ts> 
struct revert_type_seq 
{ 
    using type = std::tuple<>; 
}; 

template<typename T, typename... Ts> 
struct revert_type_seq<T, Ts...> 
{ 
    using type = typename append_to_type_seq< 
     T, 
     typename revert_type_seq<Ts...>::type 
     >::type; 
}; 

Un piccolo programma di test:

int main() 
{ 
    static_assert(
     std::is_same< 
      revert_type_seq<char, int, bool>::type, 
      std::tuple<bool, int, char> 
      >::value, 
     "Error" 
     ); 
} 

E un live example.


Secondo passo: ritornando una tupla


Il passo successivo consiste nel ritornando una tupla.Data la solita macchine indici trucco:

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

namespace detail 
{ 
    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...> 
    { }; 
} 

template<int MIN, int MAX> 
using index_range = typename detail::range_builder<MIN, MAX>::type; 

Insieme con le funzioni sopra definiti, una tupla può essere facilmente ripristinata questo modo:

template<typename... Args, int... Is> 
typename revert_type_seq<Args...>::type 
revert_tuple(std::tuple<Args...> t, index_list<Is...>) 
{ 
    using reverted_tuple = typename revert_type_seq<Args...>::type; 

    // Forwarding machinery that handles both lvalues and rvalues... 
    auto rt = std::forward_as_tuple(
      std::forward< 
       typename std::conditional< 
        std::is_lvalue_reference< 
         typename std::tuple_element<Is, reverted_tuple>::type 
         >::value, 
        typename std::tuple_element<Is, reverted_tuple>::type, 
        typename std::remove_reference< 
         typename std::tuple_element<Is, reverted_tuple>::type 
         >::type 
        >::type 
       >(std::get<sizeof...(Args) - Is - 1>(t))... 
     ); 

    return rt; 
} 

template<typename... Args> 
typename revert_type_seq<Args...>::type 
revert_tuple(std::tuple<Args...> t) 
{ 
    return revert_tuple(t, index_range<0, sizeof...(Args)>()); 
} 

Ecco un semplice programma di test:

#include <iostream> 

int main() 
{ 
    std::tuple<int, int, char> t(42, 1729, 'c'); 
    auto rt = revert_tuple(t); 

    std::cout << std::get<0>(rt) << " "; // Prints c 
    std::cout << std::get<1>(rt) << " "; // Prints 1729 
    std::cout << std::get<2>(rt) << " "; // Prints 42 
} 

Questo è un live example.


Terzo passo: ritornando argomenti di una funzione


Il passo finale consiste nel disimballaggio tupla quando si chiama la nostra funzione di destinazione. Ecco un altro programma di utilità generica per salvare noi un paio di righe:

template<typename... Args> 
typename revert_type_seq<Args...>::type 
make_revert(Args&&... args) 
{ 
    auto t = std::forward_as_tuple(std::forward<Args>(args)...); 
    return revert_tuple(t); 
} 

La funzione di cui sopra crea una tupla i cui elementi sono gli argomenti forniti, ma in ordine inverso. Non siamo pronti a definire il nostro obiettivo:

template<typename T> 
void ascending_print(T&& t) 
{ 
    std::cout << std::forward<T>(t) << " "; 
} 

template<typename T, typename... Args> 
void ascending_print(T&& t, Args&&... args) 
{ 
    ascending_print(std::forward<T>(t)); 
    ascending_print(std::forward<Args>(args)...); 
} 

La funzione sopra riportata (s) stampa tutti gli argomenti forniti. Ed ecco come potremmo scrivere descending_print():

template<typename T, int... Is> 
void call_ascending_print(T&& t, index_list<Is...>) 
{ 
    ascending_print(std::get<Is>(std::forward<T>(t))...); 
} 

template<typename... Args> 
void descending_print(Args&&... args) { 
    call_ascending_print(make_revert(std::forward<Args>(args)...), 
     index_range<0, sizeof...(Args)>()); 
} 

Un semplice caso di test di nuovo:

int main() 
{ 
    ascending_print(42, 3.14, "Hello, World!"); 
    std::cout << std::endl; 
    descending_print(42, 3.14, "Hello, World!"); 
} 

E naturalmente un live example.


passo finale: semplificazione


La soluzione sopra può essere non banale comprendere, ma può essere fatta banale uso, e molto flessibile. Dato un paio di funzioni generiche:

template<typename F, typename... Args, int... Is> 
void revert_call(F&& f, index_list<Is...>, Args&&... args) 
{ 
    auto rt = make_revert(std::forward<Args>(args)...); 
    f(std::get<Is>(rt)...); 
} 

template<typename F, typename... Args> 
void revert_call(F&& f, Args&&... args) 
{ 
    revert_call(f, index_range<0, sizeof...(Args)>(), 
       std::forward<Args>(args)...); 
} 

E un paio di definizioni di macro (non riuscivo a trovare un modo per creare un sovraccarico impostato per un modello di funzione, sorry):

#define MAKE_REVERT_CALLABLE(func) \ 
    struct revert_caller_ ## func \ 
    { \ 
     template<typename... Args> void operator() (Args&&... args) \ 
     { func(std::forward<Args>(args)...); } \ 
    }; 

#define REVERT_ADAPTER(func) \ 
    revert_caller_ ## func() 

diventa veramente facile da adattare qualsiasi funzione per essere chiamato con argomenti in ordine inverso:

MAKE_REVERT_CALLABLE(ascending_print) 

template<typename... Args> 
void descending_print(Args&&... args) 
{ 
    revert_call(REVERT_ADAPTER(ascending_print), std::forward<Args>(args)...); 
} 

int main() 
{ 
    ascending_print(42, 3.14, "Hello, World!"); 
    std::cout << std::endl; 
    descending_print(42, 3.14, "Hello, World!"); 
} 

per concludere, come di consueto, un live example.

+1

"Non sono riuscito a trovare un modo per creare un set di sovraccarico per un modello di funzione" - Beh, mio ​​caro amico, hai [questo] (https://dl.dropbox.com/u/14327987/lifting_lambdas.htm) ! Speriamo in C++ 14, comunque. :) – Xeo

+0

@Xeo: Spero davvero che ce l'avremo! –

+0

Anche se mi piace il tuo obiettivo di mantenere l'implementazione di 'descending_print' semplice senza molto codice boilerplate, devo cercare nelle altre soluzioni se questa complessità è davvero necessaria. Anche se mi piace l'idea "MAKE_REVERT_CALLABLE', che non avrei mai pensato, (eccetto che si tratta di una macro, * smile *) ho bisogno di capire perché è necessario. – towi

16

Penso invece di invertire gli argomenti, è possibile invertire la logica! Ad esempio invertire le operazioni sugli argomenti.

template <typename T> 
void ascendingPrint(const T& x) 
{ 
    cout << x << " "; 
} 

template<typename T, typename ... Args> 
void ascendingPrint(const T& t, Args... args) 
{ 
    ascendingPrint(t);     // First print `t` 
    ascendingPrint(args...);    // Then print others `args...` 
} 

template <typename T> 
void descendingPrint(const T& x) 
{ 
    cout << x << " "; 
} 

template<typename T, typename ... Args> 
void descendingPrint(const T& t, Args... args) 
{ 
    descendingPrint(args...);   // First print others `args...` 
    descendingPrint(t);     // Then print `t` 
} 

int main() 
{ 
    ascendingPrint(1, 2, 3, 4); 
    cout << endl; 
    descendingPrint(1, 2, 3, 4); 
} 

uscita

1 2 3 4 
4 3 2 1 
+0

Mi piace ... è molto più facile da leggere. :) Mi preoccupo un po 'della ricorsione, però. C'è qualcosa che promette che il template si espanderà a qualcosa di iterativo, o almeno di coda ricorsiva? – cHao

+1

@cHao - Perché, quanti argomenti hai davvero? Centinaia? –

+0

@BoPersson: Mi chiedo di più sulla potenziale esplosione di modelli. Ogni istanza del modello causa l'istanziazione di una versione per un sottoinsieme dei suoi argomenti. Se il compilatore non promette di essere in linea, passare una dozzina di argomenti può creare una dozzina di funzioni diverse. O non è davvero un problema? – cHao

10

Ecco il semplice approccio che ho citato nei commenti: Generazione di indici in senso inverso e disimballaggio una tupla con quello.

// reversed indices... 
template<unsigned... Is> struct seq{ using type = seq; }; 

template<unsigned I, unsigned... Is> 
struct rgen_seq : rgen_seq<I-1, Is..., I-1>{}; 

template<unsigned... Is> 
struct rgen_seq<0, Is...> : seq<Is...>{}; 

#include <tuple> 

namespace aux{ 
template<class Tup, unsigned... Is> 
void descending_print(Tup&& t, seq<Is...>) 
{ 
    ascending_print(std::get<Is>(std::forward<Tup>(t))...); 
} 
} // aux:: 

template<class... Args> 
void descending_print(Args&&... args) 
{ 
    auto t = std::forward_as_tuple(std::forward<Args>(args)...); 
    aux::descending_print(t, rgen_seq<sizeof...(Args)>{}); 
} 

Live example.

+0

Bellissima, e sei promosso da AndyProwl. Ti do anche un +1 - ma devo ammettere che avevo bisogno delle sue spiegazioni per apprezzare appieno il tuo codice. – towi

+0

@towi: Forse [questo] (http://loungecpp.wikidot.com/tips-and-tricks%3aindices) aiuta? – Xeo

3

La mia soluzione supporta l'inoltro perfetta e non comporta una ricorsione:

#include <iostream> 
#include <utility> 
#include <tuple> 

#include <cstdlib> 

template< typename ...types > 
void 
ascendingPrint(types &&... _values) 
{ 
    (std::cout << ... << std::forward<types>(_values)) << std::endl; 
} 

template< typename ...types, std::size_t ...indices > 
void 
descendingPrintHelper(std::tuple<types...> const & refs, std::index_sequence<indices...>) 
{ 
    constexpr std::size_t back_index = sizeof...(indices) - 1; 
    return ascendingPrint(std::forward< std::tuple_element_t< back_index - indices, std::tuple<types...> > >(std::get< back_index - indices >(refs))...); 
} 

template< typename ...types > 
void 
descendingPrint(types &&... _values) 
{ 
    auto const refs = std::forward_as_tuple(std::forward<types>(_values)...); 
    return descendingPrintHelper(refs, std::make_index_sequence< sizeof...(types) >{}); 
} 

int 
main() 
{ 
    ascendingPrint(1, ' ', 2, ' ', 3); 
    descendingPrint(1, ' ', 2, ' ', 3); 
    return EXIT_SUCCESS; 
} 

Live example (or even simplier).

anche i compilatori moderni possono perfettamente ottimizzare tutte le cose inutili: https://godbolt.org/g/01Qf6w

+1

Si noti che * le espressioni di piegatura * sono una caratteristica di C++ 1z e le strutture degli indici sono C++ 14. Il primo può essere emulato in C++ 11, quest'ultimo può essere implementato in C++ 11. – dyp

+0

@dyp Inoltre, è disponibile la funzione di valutazione costante per argomenti modello non di tipo, ma non so se C++ 11/4/z oppure no. – Orient

+0

Cosa intendi con * valutazione costante per argomenti modello non di tipo *? Anche C++ 03 consente di utilizzare espressioni costanti come argomenti modello non di tipo. – dyp

Problemi correlati