c++ - desempaquetar - que es un tuplas
¿Cómo dividir una tupla? (6)
Dado un
template<typename First, typename... Tail>
struct something
{
std::tuple<First, Tail...> t;
};
¿Cómo puedo obtener un std::tuple<Tail...>
que contiene todos los elementos de t
excepto el primero?
Creo que esta es una pregunta interesante en general, pero aquí está mi motivación para el contexto:
Me gustaría implementar un hash para tuplas. Utilicé esta respuesta como base. Descubrí que había un error en él, a saber, no llamar al operator()
del objeto hash con un valor:
return left() ^ right();
Debiera ser:
return left(std::get<0>(e)) ^ right(???);
Los ??? serían los elementos restantes de la tupla para continuar la creación de instancias recursiva de la plantilla. Aquí está el código completo incluyendo la parte de terminación:
#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;
}
};
}
Algo como esto:
#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);
}
Prueba:
#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;
}
Huellas dactilares:
std::tuple<int, char, bool>
std::tuple<char, bool>
Eddie loco encontró una manera de desempacar la tupla, que responde a la pregunta. Sin embargo, para la pregunta específica que hizo (es decir, el hashing de la tupla), ¿por qué no evita todas las copias de la tupla y en su lugar utiliza la recursión de la plantilla para hacer hash de cada elemento?
#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;
}
Esto hace el hashing en orden inverso, pero los xors son conmutativos, por lo que no importa.
Estaba buscando lo mismo y se me ocurrió esta solución de C ++ 14 bastante sencilla:
#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;
}
Así, head (.) Devuelve el primer elemento de una tupla y la cola (.) Devuelve una tupla nueva que contiene solo los últimos elementos N-1.
Esto es lo que pude arreglar en primer lugar. Probablemente algo mejor:
#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 a tener en cuenta es que si utilizas tu propio tipo en lugar de std :: tuple, probablemente estarías mucho mejor. La razón por la que esto es tan difícil es que parece que el estándar no especifica cómo funciona esta tupla, ya que no se da porque hereda de sí misma. La versión de impulso utiliza una cosa contraria que puede excavar. Aquí hay algo que podría estar más en línea con lo que quieres que haría que todo lo anterior sea tan simple como un reparto:
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 <>
{
};
No se ha probado, pero debe ilustrar el punto suficiente para jugar hasta que funcione. Ahora para obtener un objeto de tipo "cola" simplemente haga:
template < typename Head, typename ... Tail >
my_tuple<Tail...> cdr(my_tuple<Head,Tail...> const& mtpl) { return mtpl; }
Usando la respuesta de kgadek para obtener parte de std :: tuple , y el código de prueba de Andre Bergner. Esto es agradable y simple, pero no sé si es portátil.
// 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;
}
Usando una "tupla de índice" para desempaquetar la tupla sin recursión:
#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...>()); }
Consulte https://gitlab.com/redistd/redistd/blob/master/include/redi/index_tuple.h para make_index_tuple
e index_tuple
que son utilidades esenciales de la index_tuple
para trabajar con tuplas y plantillas de clases variadas similares. (Una utilidad similar se estandarizó como std::index_sequence
en C ++ 14, consulte index_seq.h para una implementación de C ++ 11 independiente).
Alternativamente, una versión sin copia que utiliza std::tie
para obtener una tupla de referencias a la cola, en lugar de hacer copias de cada 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...>()); }