Почему нет кусочной конструкции кортежа?
Стандартные шаблоны std::pair
и std::array
являются особыми случаями std::tuple
, и разумно, что они должны иметь очень похожий набор возможностей.
Однако, однозначно среди трех, std::pair
допускает кусочную конструкцию. То есть, если типы T1
и T2
могут быть построены из набора аргументов a1, a2, ...
и b1, b2, ...
, то, с моральной точки зрения, мы можем сделать пару
"pair<T1, T2> p(a1, a2, ..., b1, b2, ...)"
непосредственно. Практически это объясняется примерно так:
std::pair<T1, T2> p(std::piecewise_construct,
std::forward_as_tuple(a1, a2, ...),
std::forward_as_tuple(b1, b2, ...));
Вопрос: Почему не существует одинаковой кусочной конструктивности для массивов и кортежей? Есть ли глубокая причина, или это простое упущение? Например, было бы неплохо иметь:
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, ...));
Есть ли причина, по которой это невозможно сделать? [Edit: Или я полностью не понимаю цель кусочной конструкции?]
(У меня действительно есть ситуация, в которой я хотел бы инициализировать вектор кортежей с дефолтным значением элемента, который я бы предпочел бы построить непосредственно из аргументов, не повторяя каждый тип элемента tuple снова.)
Ответы
Ответ 1
Вопрос: Почему не существует одинаковой кусочной конструктивности для массивов и кортежей?
Мое воспоминание состоит в том, что кусочная конструкция была добавлена к std::pair
только по одной причине: поддерживать конструкцию использования-распределителя парных элементов, то есть разрешить предоставление распределителя и условно передать элементам, если они поддерживают конструкцию с распределитель (см. [allocator.uses] в стандарте).
В один момент во время процесса С++ 0x std::pair
было в два раза больше конструкторов, чем сейчас, причем каждый конструктор имеет соответствующую версию с расширенным распределением, принимающую std::allocator_arg_t
и аргумент распределителя, например
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.
Было что-то вроде бегущей шутки (ха-ха, только серьезно) о безумной сложности std::pair
. Поддержка передачи распределителей элементам была удалена из std::pair
и перенесена в std::scoped_allocator_adaptor
, которая отвечает за обнаружение того, должны ли элементы быть построены с помощью распределителя (см. Перегрузки construct
с указателем на std::pair
в [allocator.adaptor.members]).
Хорошим следствием кусочной конструкции является то, что вы можете выполнить инициализацию стиля "emplace" для парных элементов, предоставляя пару недвижных, не копируемых типов, но насколько я знаю, это не было целью дизайна.
Таким образом, причина tuple
не поддерживает, что эта функция была изобретена для упрощения pair
, которая была выпущена из очень простого типа в С++ 03 в смеющийся запас в С++ 0x, но то же самое для tuple
не считалось важным (в любом случае это было новым для С++ 11). Кроме того, расширение scoped_allocator_adaptor
для обработки наборов произвольных чисел элементов сделало бы этот адаптер намного более сложным.
Что касается std::array
, то этот тип агрегата (потому что причины), поэтому добавление конструктора, принимающего piecewise_construct_t
, невозможно, не делая его неагрегатом.
Ответ 2
Я не уверен, почему этого не произошло. Раньше я думал, что реализация будет невозможна, учитывая текущий синтаксис шаблона varadic, но я понял, что это можно сделать, если оно разбито на части.
Если они определили такой интерфейс:
template<typename... T>
tuple(piecewise_construct, T&&... t);
И потребовалось, чтобы аргументы являлись тем, что вы можете использовать std::get<N>
для доступа к аргументам (в основном, кортежей, пар, массивов). Там должны быть дополнительные проверки, чтобы проверить, что не существует несоответствия между количеством аргументов и количеством элементов в кортеже.
Изменить: эта проблема беспокоила меня, так как я ее читал. И я создал следующий класс, он получен из std::tuple
и не имеет элементов данных, поэтому вы можете назначить его кортежу, а срез безвреден. Текущая версия требует, чтобы элементы были подвижными или скопируемыми, поскольку они создают временную и затем вставляют ее в кортеж. Если вы являетесь исполнителем кортежа, должно быть возможно исключить даже этот ход.
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;
}
Ответ 3
Вот моя реализация кусочка кусочка (это также позволяет опустить значения с omit
"ключевым словом" ). Нулевой накладные расходы (без копирования/перемещения - прямая конструкция):
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 );
}
Использование:
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;
Кортеж внутри кортежа (нулевые служебные данные):
TuplePiecewise< string, TuplePiecewise<int,int> > list4(forward_as_tuple("RRR"), forward_as_tuple(forward_as_tuple(10), forward_as_tuple(20)));