2012-05-16 32 views
15

dato unCome dividere una tupla?

template<typename First, typename... Tail> 
    struct something 
    { 
     std::tuple<First, Tail...> t; 
    }; 

Come posso ottenere un std::tuple<Tail...> che contiene tutti gli elementi da t tranne che per il primo?


credo che questo sia una domanda interessante in generale, ma qui è la mia motivazione per il contesto:

mi piacerebbe realizzare un hash per le tuple. Ho usato this answer come base. Ho scoperto che c'è stato un errore in esso, vale a dire non chiamare operator() dell'oggetto hash con un valore:

return left()^right(); 

dovrebbe essere:

return left(std::get<0>(e))^right(???); 

Il ??? sarebbero gli elementi rimanenti della tupla per continuare l'istanza ricorsiva del modello. Ecco il codice completo compresa la parte di terminazione:

#include <functional> 
#include <utility> 

namespace std 
{ 

template<typename First, typename... Tail> 
struct hash<std::tuple<First, Tail...>> 
{ 
    typedef size_t result_type; 
    typedef std::tuple<First, Tail...> argument_type; 

    result_type operator()(argument_type const& e) const 
    { 
     std::hash<First> left; 
     std::hash<std::tuple<Tail...>> right; 
     return left(std::get<0>(e))^right(???); 
    } 
}; 

template<> 
struct hash<std::tuple<>> 
{ 
    typedef size_t result_type; 
    typedef std::tuple<> argument_type; 

    result_type operator()(argument_type const& e) const 
    { 
     return 1; 
    } 
}; 

} 

risposta

1

Qualcosa di simile a questo:

#include <tuple> 

template <bool, typename T, unsigned int ...N> struct tail_impl; 

template <typename T, typename ...Args, unsigned int ...N> 
struct tail_impl<false, std::tuple<T, Args...>, N...> 
{ 
    static std::tuple<Args...> go(std::tuple<T, Args...> const & x) 
    { 
     return tail_impl<sizeof...(N) + 1 == sizeof...(Args), std::tuple<T, Args...>, N..., sizeof...(N)>::go(x); 
    } 
}; 

template <typename T, typename ...Args, unsigned int ...N> 
struct tail_impl<true, std::tuple<T, Args...>, N...> 
{ 
    static std::tuple<Args...> go(std::tuple<T, Args...> const & x) 
    { 
     return std::tuple<Args...>(std::get<N>(x)...); 
    } 
}; 

template <typename T, typename ...Args> 
std::tuple<Args...> tail(std::tuple<T, Args...> const & x) 
{ 
    return tail_impl<sizeof...(Args) == 1, std::tuple<T, Args...>, 0>::go(x); 
} 

prova:

#include <demangle.hpp> 
#include <iostream> 

typedef std::tuple<int, char, bool> TType; 

int main() 
{ 
    std::cout << demangle<TType>() << std::endl; 
    std::cout << demangle<decltype(tail(std::declval<TType>()))>() << std::endl; 
} 

Stampe:

std::tuple<int, char, bool> 
std::tuple<char, bool> 
+2

Il mio test: 'int main() {std :: tuple t1 (42, 'e', ​​16.7); std :: tupla t2 = tail (t1); std :: cout << std::get<0> (t2) << std :: endl; } 'non riesce a compilare:' errore: impossibile chiamare la funzione membro 'std :: tupla tail_impl , N ...> :: vai (const std :: tuple &) [con T = int, Args = {char, double}, unsigned int ... N = {0u}] 'without object' –

+0

Scusa, ci sono stati alcuni errori. Risolto ora. –

3

Questo è quello che ho potrebbe battere fuori prima prova. qualcosa di meglio Probabile:

#include <tuple> 

template < typename Target, typename Tuple, int N, bool end > 
struct builder 
{ 
    template < typename ... Args > 
    static Target create(Tuple const& t, Args && ... args) 
    { 
     return builder<Target,Tuple, N+1, std::tuple_size<Tuple>::value == N+1>::create(t, std::forward<Args>(args)..., std::get<N>(t)); 
    } 
}; 

template < typename Target, typename Tuple, int N > 
struct builder<Target,Tuple,N,true> 
{ 
    template < typename ... Args > 
    static Target create(Tuple const& t, Args && ... args) { return Target(std::forward<Args>(args)...); } 
}; 

template < typename Head, typename ... Tail > 
std::tuple<Tail...> cdr(std::tuple<Head,Tail...> const& tpl) 
{ 
    return builder<std::tuple<Tail...>, std::tuple<Head,Tail...>, 1, std::tuple_size<std::tuple<Head,Tail...>>::value == 1>::create(tpl); 
} 

#include <iostream> 
int main() { 
    std::tuple<int,char,double> t1(42,'e',16.7); 
    std::tuple<char,double> t2 = cdr(t1); 

    std::cout << std::get<0>(t2) << std::endl; 
} 

Una cosa da notare è che se si è utilizzato il proprio tipo, invece di std :: tuple si sarebbe probabilmente molto meglio. Il motivo per cui questo è così difficile è che sembra che lo standard non specifichi come funziona questa tupla in quanto non è dato che erediti da se stessa. La versione potenziata usa un coso che puoi scavare. Ecco qualcosa che potrebbe essere più in linea con ciò che si vuole che renderebbe facendo tutto quanto sopra semplice come un cast:

template < typename ... Args > struct my_tuple; 

template < typename Head, typename ... Tail > 
struct my_tuple<Head,Tail...> : my_tuple<Tail...> 
{ 
    Head val; 
    template < typename T, typename ... Args > 
    my_tuple(T && t, Args && ... args) 
     : my_tuple<Tail...>(std::forward<Args>(args)...) 
     , val(std::forward<T>(t)) 
    {} 
}; 

template < > 
struct my_tuple <> 
{ 
}; 

Questo è testato, ma dovrebbe illustrare il punto abbastanza per giocare con finché non funziona. Ora per ottenere un oggetto di tipo "coda" è sufficiente fare:

template < typename Head, typename ... Tail > 
my_tuple<Tail...> cdr(my_tuple<Head,Tail...> const& mtpl) { return mtpl; } 
3

Pazzo Eddie ha trovato un modo per decomprimere la tupla, che non risponde alla domanda. Tuttavia, per la domanda specifica che hai chiesto (cioè l'hashing della tupla), perché non evitare tutte le copie di tupla e invece utilizzare la ricorsione del modello per cancellare ogni elemento a turno?

#include <utility> 
#include <iostream> 

template< typename T > 
size_t left(T const &) { 
    return 1; 
} 

template< int N, typename Head, typename... Tail > 
struct _hash { 
    typedef size_t result_type; 
    typedef std::tuple< Head, Tail... > argument_type; 

    result_type operator()(argument_type const &e) const { 
    return left(std::get<N>(e))^_hash<N-1, Head, Tail... >()(e); 
    } 
}; // end struct _hash 

template< typename Head, typename... Tail > 
struct _hash< 0, Head, Tail... > { 
    typedef size_t result_type; 
    typedef std::tuple< Head, Tail... > argument_type; 

    result_type operator()(argument_type const &e) const { 
    return left(std::get<0>(e)); 
    } 
}; // end struct _hash<0> 

template< typename Head, typename... Tail > 
size_t hash(std::tuple< Head, Tail... > const &e) { 
    return _hash< sizeof...(Tail), Head, Tail... >()(e); 
} 

int main() { 
    std::tuple<int> l_tuple(5); 
    std::cout << hash(l_tuple) << std::endl; 
} 

Questo esegue l'hashing in ordine inverso, ma gli xors sono commutativi quindi non importa.

+0

non dovrebbe essere 'return _hash () (e);'? std :: get nella prima profondità di ricorsione fallirà altrimenti. –

+0

Io non la penso così. La dimensione della coda è uno in meno del numero di elementi nella tupla, quindi sizeof ... (Tail) dovrebbe essere l'indice dell'ultimo elemento nella coda. In altre parole, gli indici nell'intervallo [0, sizeof ... (Tail)] dovrebbero essere validi qui. –

+0

Ah, mi sono perso. –

4

L'utilizzo di un "indice di tuple" per decomprimere il tuple senza ricorsione:

#include <redi/index_tuple.h> 

template<typename T, typename... U, unsigned... I> 
    inline std::tuple<U...> 
    cdr_impl(const std::tuple<T, U...>& t, redi::index_tuple<I...>) 
    { return std::tuple<U...>{ std::get<I+1>(t)... }; } 

template<typename T, typename... U> 
    inline std::tuple<U...> 
    cdr(const std::tuple<T, U...>& t) 
    { return cdr_impl(t, redi::to_index_tuple<U...>()); } 

Vedi https://gitlab.com/redistd/redistd/blob/master/include/redi/index_tuple.h per make_index_tuple e index_tuple che sono IMHO servizi essenziali per lavorare con le tuple e simili modelli di classe variadic. (Un'utilità simile è stata standardizzata come std::index_sequence in C++ 14, vedere index_seq.h per un'implementazione standalone C++ 11).

In alternativa, un non-copia di versione utilizzando std::tie per ottenere una tupla di riferimenti alla coda, invece di fare copie di ogni elemento:

#include <redi/index_tuple.h> 

template<typename T, typename... U, unsigned... I> 
    inline std::tuple<const U&...> 
    cdr_impl(const std::tuple<T, U...>& t, redi::index_tuple<I...>) 
    { return std::tie(std::get<I+1>(t)...); } 

template<typename T, typename... U> 
    inline std::tuple<const U&...> 
    cdr(const std::tuple<T, U...>& t) 
    { return cdr_impl(t, redi::to_index_tuple<U...>()); } 
+2

È possibile abbinare il pacchetto di indici con 'redi :: index_tuple <0, I...>' e 'I' inizierà dall'indice' 1' su, cioè solo quegli indici a cui teniamo. –

+0

Stai ancora utilizzando la ricorsione solo nelle utility index_tuple.h che stai utilizzando. –

+0

@Luc Danton, buona idea, ma hai bisogno anche di altre modifiche o il pacchetto di parametri I non avrà le stesse dimensioni di U –

8

ero alla ricerca per la stessa cosa e si avvicinò con questo piuttosto semplice C++ 14 soluzione: (.) ​​(.)

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

template < typename T , typename... Ts > 
auto head(std::tuple<T,Ts...> t) 
{ 
    return std::get<0>(t); 
} 

template < std::size_t... Ns , typename... Ts > 
auto tail_impl(std::index_sequence<Ns...> , std::tuple<Ts...> t) 
{ 
    return std::make_tuple(std::get<Ns+1u>(t)...); 
} 

template < typename... Ts > 
auto tail(std::tuple<Ts...> t) 
{ 
    return tail_impl(std::make_index_sequence<sizeof...(Ts) - 1u>() , t); 
} 

int main() 
{ 
    auto t = std::make_tuple(2, 3.14 , 'c'); 
    std::cout << head(t) << std::endl; 
    std::cout << std::get<0>(tail(t)) << std::endl; 
    std::cout << std::get<1>(tail(t)) << std::endl; 
} 

Quindi, testa restituisce il primo elemento di una tupla e coda restituisce una nuova tupla contenente solo gli ultimi N-1 elementi.

0

Utilizzo della risposta di kgadek a get part of std::tuple e codice di prova di Andre Bergner. Questo è bello e semplice, ma non so se è portatile.

// works using gcc 4.6.3 
// g++ -std=c++0x -W -Wall -g main.cc -o main 
#include <iostream> 
#include <tuple> 

template < typename T , typename... Ts > 
const T& head(std::tuple<T,Ts...> t) 
{ 
    return std::get<0>(t); 
} 

template <typename T, typename... Ts> 
const std::tuple<Ts...>& tail(const std::tuple<T, Ts...>& t) 
{ 
    return (const std::tuple<Ts...>&)t; 
} 

int main() 
{ 
    auto t = std::make_tuple(2, 3.14 , 'c'); 
    std::cout << head(t) << std::endl; 
    std::cout << std::get<0>(tail(t)) << std::endl; 
    std::cout << std::get<1>(tail(t)) << std::endl; 
}