2013-05-05 4 views
22

La mia domanda è nel codice:Template tuple - chiamando una funzione su ogni elemento

template<typename... Ts> 
struct TupleOfVectors { 
    std::tuple<std::vector<Ts>...> tuple; 

    void do_something_to_each_vec() { 
    //Question: I want to do this: 
    // "for each (N)": do_something_to_vec<N>() 
    //How? 
    } 

    template<size_t N> 
    void do_something_to_vec() { 
    auto &vec = std::get<N>(tuple); 
    //do something to vec 
    } 
}; 
+1

Possibile duplicato di [itera su tupla] (https://stackoverflow.com/questions/1198260/iterate-over-tuple) – Justin

risposta

26

Si può facilmente farlo con alcuni indici macchinari. Data una meta-funzione di gen_seq per la generazione di sequenze di interi a tempo di compilazione (incapsulato dal modello seq classe):

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

    template<int N, int... Is> 
    struct gen_seq : gen_seq<N - 1, N - 1, Is...> { }; 

    template<int... Is> 
    struct gen_seq<0, Is...> : seq<Is...> { }; 
} 

E i seguenti modelli di funzione:

#include <tuple> 

namespace detail 
{ 
    template<typename T, typename F, int... Is> 
    void for_each(T&& t, F f, seq<Is...>) 
    { 
     auto l = { (f(std::get<Is>(t)), 0)... }; 
    } 
} 

template<typename... Ts, typename F> 
void for_each_in_tuple(std::tuple<Ts...> const& t, F f) 
{ 
    detail::for_each(t, f, detail::gen_seq<sizeof...(Ts)>()); 
} 

È possibile utilizzare la funzione for_each_in_tuple di sopra di questo way:

#include <string> 
#include <iostream> 

struct my_functor 
{ 
    template<typename T> 
    void operator() (T&& t) 
    { 
     std::cout << t << std::endl; 
    } 
}; 

int main() 
{ 
    std::tuple<int, double, std::string> t(42, 3.14, "Hello World!"); 
    for_each_in_tuple(t, my_functor()); 
} 

Ecco uno live example.

Nella vostra situazione concreta, questo è come si potrebbe usare:

template<typename... Ts> 
struct TupleOfVectors 
{ 
    std::tuple<std::vector<Ts>...> t; 

    void do_something_to_each_vec() 
    { 
     for_each_in_tuple(t, tuple_vector_functor()); 
    } 

    struct tuple_vector_functor 
    { 
     template<typename T> 
     void operator() (T const &v) 
     { 
      // Do something on the argument vector... 
     } 
    }; 
}; 

E ancora una volta, ecco un live example.

+4

metaprogramming mi sta mangiando il cervello. non so da dove iniziano le cose, cosa sta chiamando cosa, e cosa/dove il risultato finale è. non piace affatto la programmazione regolare. Ci vorrà del tempo per decifrarlo :) – 7cows

+2

@ 7cows: certo. All'inizio non è affatto facile, e forse questo non è esattamente l'esempio più facile da iniziare, ma sono sicuro che con qualche pratica lo afferrerai presto;) –

+1

'void for_each (T && t, F f, seq ) ': Perché l'ultimo argomento non ha un identificatore? –

6

Ecco uno approccio che può funzionare bene nel tuo caso:

template<typename... Ts> 
struct TupleOfVectors { 
    std::tuple<std::vector<Ts>...> tuple; 

    void do_something_to_each_vec() 
    { 
     // First template parameter is just a dummy. 
     do_something_to_each_vec_helper<0,Ts...>(); 
    } 

    template<size_t N> 
    void do_something_to_vec() 
    { 
     auto &vec = std::get<N>(tuple); 
     //do something to vec 
    } 

private: 
    // Anchor for the recursion 
    template <int> 
    void do_something_to_each_vec_helper() { } 

    // Execute the function for each template argument. 
    template <int,typename Arg,typename...Args> 
    void do_something_to_each_vec_helper() 
    { 
     do_something_to_each_vec_helper<0,Args...>(); 
     do_something_to_vec<sizeof...(Args)>(); 
    } 
}; 

L'unica cosa che è un po 'disordinato qui è il manichino parametro extra int modello da do_something_to_each_vec_helper. È necessario rendere il do_something_to_each_vec_helper ancora un modello quando non rimangono argomenti. Se avessi un altro parametro di modello che volevi usare, potresti usarlo lì.

+0

È piuttosto brillante come questo riesca a chiamare il metodo "do_something_to_vec' in corretto 0,1,2, ... ordine no? Mi piace ... :) – 7cows

+0

@ 7cows: grazie! –

+0

'template void do_something_to_each_vec_helper() {do_something_to_vec <)(); template void do_something_to_each_vec_helper() {do_something_to_each_vec_helper (); do_something_to_vec (); } 'cancella quel' int' inutilizzato, ma viola DRY (non ripetersi) in una certa misura. Hmm. – Yakk

5

Se non si è particolarmente sposata ad una soluzione sotto forma di generica "per ogni" modello di funzione, è possibile utilizzare uno come questo:

#ifndef TUPLE_OF_VECTORS_H 
#define TUPLE_OF_VECTORS_H 

#include <vector> 
#include <tuple> 
#include <iostream> 

template<typename... Ts> 
struct TupleOfVectors 
{ 
    std::tuple<std::vector<Ts>...> tuple; 

    template<typename ...Args> 
    TupleOfVectors(Args... args) 
    : tuple(args...){} 

    void do_something_to_each_vec() { 
     do_something_to_vec(tuple); 
    } 

    template<size_t I = 0, class ...P> 
    typename std::enable_if<I == sizeof...(P)>::type 
    do_something_to_vec(std::tuple<P...> &) {} 

    template<size_t I = 0, class ...P> 
    typename std::enable_if<I < sizeof...(P)>::type 
    do_something_to_vec(std::tuple<P...> & parts) { 
     auto & part = std::get<I>(tuple); 
     // Doing something... 
     std::cout << "vector[" << I << "][0] = " << part[0] << std::endl; 
     do_something_to_vec<I + 1>(parts); 
    } 
}; 

#endif // EOF 

Un programma di test, costruito con GCC 4.7.2 e clang 3.2:

#include "tuple_of_vectors.h" 

using namespace std; 

int main() 
{ 
    TupleOfVectors<int,int,int,int> vecs(vector<int>(1,1), 
     vector<int>(2,2), 
     vector<int>(3,3), 
     vector<int>(4,4)); 

    vecs.do_something_to_each_vec(); 
    return 0; 
} 

Lo stesso stile di ricorsione può essere utilizzato in un "for_each" generico modello funzione senza apparato indici ausiliari:

#ifndef FOR_EACH_IN_TUPLE_H 
#define FOR_EACH_IN_TUPLE_H 

#include <type_traits> 
#include <tuple> 
#include <cstddef> 

template<size_t I = 0, typename Func, typename ...Ts> 
typename std::enable_if<I == sizeof...(Ts)>::type 
for_each_in_tuple(std::tuple<Ts...> &, Func) {} 

template<size_t I = 0, typename Func, typename ...Ts> 
typename std::enable_if<I < sizeof...(Ts)>::type 
for_each_in_tuple(std::tuple<Ts...> & tpl, Func func) 
{ 
    func(std::get<I>(tpl)); 
    for_each_in_tuple<I + 1>(tpl,func); 
} 

#endif //EOF 

E un programma di test per questo:

#include "for_each_in_tuple.h" 
#include <iostream> 

struct functor 
{ 
    template<typename T> 
    void operator() (T&& t) 
    { 
     std::cout << t << std::endl; 
    } 
}; 

int main() 
{ 
    auto tpl = std::make_tuple(1,2.0,"Three"); 
    for_each_in_tuple(tpl,functor()); 
    return 0; 
} 
+0

questa risposta è stata molto più utile per me, e non genera avvisi come alcuni degli altri. +1 –

1

stavo testando con tuple e metaprogrammazione e trovato il thread corrente. Penso che il mio lavoro possa ispirare qualcun altro anche se mi piace la soluzione di @Andy.

In ogni caso, divertiti!

#include <tuple> 
#include <type_traits> 
#include <iostream> 
#include <sstream> 
#include <functional> 

template<std::size_t I = 0, typename Tuple, typename Func> 
typename std::enable_if< I != std::tuple_size<Tuple>::value, void >::type 
for_each(const Tuple& tuple, Func&& func) 
{ 
    func(std::get<I>(tuple)); 
    for_each<I + 1>(tuple, func); 
} 

template<std::size_t I = 0, typename Tuple, typename Func> 
typename std::enable_if< I == std::tuple_size<Tuple>::value, void >::type 
for_each(const Tuple& tuple, Func&& func) 
{ 
    // do nothing 
} 


struct print 
{ 
    template<typename T> 
    void operator() (T&& t) 
    { 
     std::cout << t << std::endl; 
    } 
}; 

template<typename... Params> 
void test(Params&& ... params) 
{ 
    int sz = sizeof...(params); 
    std::tuple<Params...> values(std::forward<Params>(params)...); 
    for_each(values, print()); 
} 


class MyClass 
{ 
public: 
    MyClass(const std::string& text) 
     : m_text(text) 
    { 
    } 

    friend std::ostream& operator <<(std::ostream& stream, const MyClass& myClass) 
    { 
     stream << myClass.m_text; 
     return stream; 
    } 

private: 
    std::string m_text; 
}; 


int main() 
{ 
    test(1, "hello", 3.f, 4, MyClass("I don't care")); 
} 
+0

Grande pezzo di codice! Tuttavia, come-non funziona con lambda in linea in quanto si aspetta un valore l 'func'. La firma della funzione deve essere modificata in 'for_each (const Tuple & tuple, Func && func)', con un argomento 'Func && func' per consentire il passaggio di un lambda temporaneo. –

+0

Hai ragione, ben notato: D – dmayola

14

In C++ 17 si può fare questo:

std::apply([](auto ...x){std::make_tuple(some_function(x)...);} , the_tuple); 

dato che some_function ha sovraccarichi adatti per tutti i tipi nella tupla.

Questo funziona già in Clang ++ 3.9, usando std :: experimental :: apply.

+3

non conduce questa all'iterazione - cioè chiamate di 'fa_qualcosa()' - che si verificano in un ordine specificato, poiché il parametro pacco è espanso all'interno di una chiamata di funzione() ', in cui gli argomenti hanno un ordinamento non specificato? Questo potrebbe essere molto significativo; Immagino che la maggior parte delle persone si aspetterebbe che l'ordinamento sia garantito nello stesso ordine dei membri, cioè come indici di 'std :: get <>()'. AFAIK, per ottenere l'ordine garantito in casi come questo, l'espansione deve essere eseguita all'interno di '{parentesi}'. Ho sbagliato?Questa risposta pone l'accento su tale ordinamento: http://stackoverflow.com/a/16387374/2757035 –

+0

@underscore_d C++ 17 garantisce l'ordine di esecuzione degli argomenti della funzione. Poiché questa risposta si applica a C++ 17, questo è valido. –

+0

@GuillaumeRacicot Sono a conoscenza di alcune modifiche all'ordine/garanzie di valutazione in determinati contesti, ma non agli argomenti delle funzioni, a parte qualche back-and-out in cui l'ordine da sinistra a destra è stato _considerato_ ma è stato rifiutato. Guarda la bozza attuale dello standard: https://github.com/cplusplus/draft/blob/master/source/expressions.tex#L1621 'L'inizializzazione di un parametro, incluso ogni calcolo del valore associato ed effetto collaterale, è sequenzialmente indeterminato rispetto a quello di qualsiasi altro parametro. –

1

Oltre a the answer di @M. Alaggan, se è necessario chiamare una funzione su elementi tuple in ordine di apparizione nella tupla, in 17 è anche possibile utilizzare un'espressione piega C++ in questo modo:

std::apply([](auto ...x){(..., some_function(x));}, the_tuple); 

(live example).