una tuplas tupla que c++ c++11 tuples piecewise

c++ - tuplas - que es una tupla



¿Por qué no hay construcción de tuplas por partes? (3)

Las plantillas estándar std::pair y std::array son casos especiales de std::tuple , y es lógico que tengan un conjunto muy similar de capacidades.

Sin embargo, únicamente entre los tres, std::pair permite la construcción por partes . Es decir, si los tipos T1 y T2 se pueden construir a partir de un conjunto de argumentos a1, a2, ... y b1, b2, ... , entonces, moralmente hablando, podemos hacer un par

"pair<T1, T2> p(a1, a2, ..., b1, b2, ...)"

directamente. Prácticamente, esto se explica de la siguiente manera:

std::pair<T1, T2> p(std::piecewise_construct, std::forward_as_tuple(a1, a2, ...), std::forward_as_tuple(b1, b2, ...));

Pregunta: ¿Por qué no existe la misma constructibilidad por partes para matrices y tuplas? ¿Hay una razón profunda, o es esto una omisión simple? Por ejemplo, sería bueno tener:

std::tuple<T1, T2, T3> t(std::piecewise_construct, std::forward_as_tuple(a1, a2, ...), std::forward_as_tuple(b1, b2, ...), std::forward_as_tuple(c1, c2, ...));

¿Hay alguna razón por la cual esto no se puede hacer? [ Editar : ¿O estoy malinterpretando por completo el propósito de la construcción por partes?]

(Realmente tengo una situación en la que me gustaría inicializar un vector de tuplas con un valor de elemento predeterminado que preferiría construir directamente a partir de los argumentos, sin especificar cada tipo de elemento de tupla).


Pregunta: ¿Por qué no existe la misma constructibilidad por partes para matrices y tuplas?

Mi recuerdo es que la construcción por partes se agregó a std::pair por una única razón: para admitir la construcción de los elementos de asignación de los dos elementos, es decir, permitir que se proporcione un asignador y pasarlo condicionalmente a los elementos si admiten la construcción con un asignador (ver [allocator.uses] en el estándar).

En un punto durante el proceso C ++ 0x std::pair tenía el doble de constructores que ahora, con cada constructor que tiene una versión correspondiente "allocator-extended" que toma un std::allocator_arg_t y un argumento de asignación, por ejemplo

template<class T, class U> struct pair { pair(); pair(allocator_arg_t, const Alloc&); template<class TT, class UU> pair(TT&&, UU&&); template<class Alloc, class TT, class UU> pair(allocator_arg_t, const Alloc&, TT&&, UU&&); // etc.

Había algo así como una broma (jaja, solo serio) sobre la complejidad insana de std::pair . El soporte para pasar asignadores a los elementos se eliminó de std::pair y se movió a std::scoped_allocator_adaptor , que es responsable de detectar si los elementos deben construirse con un asignador (ver las sobrecargas de construct tomando un puntero a std::pair en [allocator.adaptor.members]).

Una buena consecuencia de la construcción por partes es que puede hacer una inicialización de estilo "emplace" de elementos de pares, permitiendo pares de tipos no movibles, no copiables, pero hasta donde sé, ese no era el objetivo del diseño.

Por lo tanto, la razón por la que la tuple no lo admite es que la función se inventó para simplificar el pair que se había disparado de un tipo muy simple en C ++ 03 a un hazmerreír en C ++ 0x, pero no se consideró hacer lo mismo para la tuple tan importante (era nuevo para C ++ 11 de todos modos). Además, extender scoped_allocator_adaptor para manejar tuplas de números arbitrarios de elementos habría hecho que ese adaptador sea mucho más complicado.

En cuanto a std::array , es un tipo agregado (porque tiene razones), por lo que no es posible agregar un constructor que tome piecewise_construct_t sin hacerlo no agregado.


Aquí está mi implementación de tuple por partes (también permite omitir valores con omit "palabra clave"). Cero sobrecarga (sin copia / movimiento - construcción directa):

http://coliru.stacked-crooked.com/a/6b3f9a5f843362e3

#include <tuple> #include <utility> #include <typeinfo> struct Omit{} omit; template <class Field, class ...Fields> struct TupleHolder{ using fieldT = Field; using nextT = TupleHolder<Fields...>; Field field; TupleHolder<Fields...> next; TupleHolder(){} template <class ...ValuesRef> TupleHolder(Omit, ValuesRef&& ... values) : next( std::forward<ValuesRef>(values)... ) {} template <std::size_t ...ids, class FieldValue, class ...ValuesRef> TupleHolder(std::index_sequence<ids...>, FieldValue&& field, ValuesRef&& ... values) : field( std::get<ids>(std::forward<FieldValue>(field))... ), next( std::forward<ValuesRef>(values)... ) {}; template <class FieldValue, class ...ValuesRef> TupleHolder(FieldValue&& field, ValuesRef&& ... values) : TupleHolder( std::make_index_sequence< std::tuple_size< std::decay_t<FieldValue> >::value >(), std::forward<FieldValue>(field), std::forward<ValuesRef>(values)... ) {} }; template <class Field> struct TupleHolder<Field>{ using fieldT = Field; Field field; // actually last TupleHolder(){} TupleHolder(Omit){} template <std::size_t ...ids, class FieldValue> TupleHolder(std::index_sequence<ids...>, FieldValue&& field) : field( std::get<ids>(std::forward<FieldValue>(field))... ) {} template <class FieldValue> TupleHolder(FieldValue&& field) : TupleHolder( std::make_index_sequence< std::tuple_size< std::decay_t<FieldValue> >::value >(), std::forward<FieldValue>(field) ) {} }; template <int index, int target_index, class T> struct GetLoop{ using type = typename T::nextT; constexpr static decltype(auto) get(T& data) noexcept{ return GetLoop<index+1, target_index, typename T::nextT>::get( data.next ); } constexpr static decltype(auto) get(const T& data) noexcept{ return GetLoop<index+1, target_index, typename T::nextT>::get( data.next ); } constexpr static decltype(auto) get(T&& data) noexcept{ return GetLoop<index+1, target_index, typename T::nextT>::get( std::forward<type>(data.next) ); } }; template <int target_index, class T> struct GetLoop<target_index, target_index, T>{ using type = typename T::fieldT; constexpr static type& get(T& data) noexcept{ return data.field; } constexpr static const type& get(const T& data) noexcept{ return data.field; } constexpr static type&& get(T&& data) noexcept{ return std::forward<type>(data.field); } }; // ---------------------------------------------------------------------------------- // F R O N T E N D // ---------------------------------------------------------------------------------- template<class ...FieldTypes> struct TuplePiecewise{ using fieldsT = TupleHolder<FieldTypes...>; TupleHolder<FieldTypes...> data; TuplePiecewise(){} // allow copy constructor TuplePiecewise(TuplePiecewise& other) : TuplePiecewise(static_cast<const TuplePiecewise&>(other)) {} template <class ...ValuesRef> explicit constexpr TuplePiecewise(ValuesRef&& ... values) noexcept : data( std::forward<ValuesRef>(values)... ){} TuplePiecewise( const TuplePiecewise& other ) = default; TuplePiecewise( TuplePiecewise&& other ) = default; static constexpr const std::size_t size = sizeof...(FieldTypes); }; template<int index, class ...FieldTypes> constexpr decltype(auto) get(TuplePiecewise<FieldTypes...> &&list) noexcept { return GetLoop<0, index, typename TuplePiecewise<FieldTypes...>::fieldsT >::get( std::move(list.data) ); } template<int index, class ...FieldTypes> constexpr decltype(auto) get(TuplePiecewise<FieldTypes...> &list) noexcept { return GetLoop<0, index, typename TuplePiecewise<FieldTypes...>::fieldsT >::get( list.data ); } template<int index, class ...FieldTypes> constexpr decltype(auto) get(const TuplePiecewise<FieldTypes...> &list) noexcept { return GetLoop<0, index, typename TuplePiecewise<FieldTypes...>::fieldsT >::get( list.data ); }

Uso:

TuplePiecewise< CopyTest, int&, string, int > list (forward_as_tuple(45,63), forward_as_tuple(i), forward_as_tuple("hghhh"), omit ); decltype(auto) o = get<2>(list); cout << o;

Tupla dentro de la tupla (cero sobrecarga):

TuplePiecewise< string, TuplePiecewise<int,int> > list4(forward_as_tuple("RRR"), forward_as_tuple(forward_as_tuple(10), forward_as_tuple(20)));


No estoy seguro de por qué no está allí. Previamente, pensé que la implementación no sería posible, dada la sintaxis actual de la plantilla varadica, pero me di cuenta de que se puede hacer si se rompe en pedazos.

Si hubieran definido una interfaz como esta:

template<typename... T> tuple(piecewise_construct, T&&... t);

Y se convirtió en un requisito que los argumentos sean algo que pueda usar std::get<N> para acceder a los argumentos (básicamente, tuplas, pares, matrices). Tendría que haber controles adicionales para verificar que no haya un desajuste entre la cantidad de argumentos dados y la cantidad de elementos en la tupla.

Editar: Este problema me ha estado molestando desde que lo leí. Y creé la siguiente clase, se deriva de std::tuple y no tiene miembros de datos, por lo que puede asignarla a la tupla y el corte es inofensivo. La versión actual requiere que los elementos se puedan mover o copiar, ya que crea un elemento temporal y luego lo inserta en la tupla. Si fue un implementador de tuplas, debería ser posible eliminar incluso ese movimiento.

namespace detail { template<int ... N> struct index { typedef index<N..., sizeof...(N)> next; }; template<int N> struct build_index { typedef typename build_index<N - 1>::type::next type; }; template<> struct build_index<0> { typedef index<> type; }; template<typename T> struct tuple_index { typedef typename build_index< std::tuple_size<typename std::remove_reference<T>::type>::value>::type type; }; } template<typename ... Elements> class piecewise_tuple: public std::tuple<Elements...> { typedef std::tuple<Elements...> base_type; template<int Index, typename ... Args, int ... N> static typename std::tuple_element<Index, base_type>::type construct(std::tuple<Args...>&& args, detail::index<N...>) { typedef typename std::tuple_element<Index, base_type>::type result_type; return result_type(std::get<N>(std::move(args))...); } template<int ...N, typename ArgTuple> piecewise_tuple(detail::index<N...>, ArgTuple&& element_args) : base_type( construct<N>( std::get<N>(std::forward<ArgTuple>(element_args)), typename detail::tuple_index< typename std::tuple_element<N, typename std::remove_reference<ArgTuple>::type >::type >::type() )...) { } public: piecewise_tuple() = default; // For non-piecewise constructors, forward them template<typename... Args> piecewise_tuple(Args&&... args) : base_type(std::forward<Args>(args)...) {} template<typename... T> piecewise_tuple(std::piecewise_construct_t, T&&... args) : piecewise_tuple(typename detail::tuple_index<base_type>::type(), std::forward_as_tuple(std::forward<T>(args)...)) { } }; // Usage example int main() { int i = 5; std::unique_ptr<int> up(new int(0)); piecewise_tuple<std::pair<int, int>, double, std::unique_ptr<int>, int& > p(std::piecewise_construct, std::forward_as_tuple(1,2), std::forward_as_tuple(4.3), std::forward_as_tuple(std::move(up)), std::forward_as_tuple(i)); return 0; }