#c #perfect-forwarding #emplace
Вопрос:
Стандартные шаблоны 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, ...));
Есть ли причина, по которой этого нельзя сделать? [Правка: Или я полностью неправильно понимаю цель кусочной конструкции?]
(У меня действительно есть ситуация, в которой я хотел бы инициализировать вектор кортежей со значением элемента по умолчанию, которое я предпочел бы построить непосредственно из аргументов, без повторного указания типа каждого элемента кортежа.)
Комментарии:
1. Он есть в N3059 и снова исчез в N3140. Теперь, если бы человек мог просто выяснить, какой отчет на самом деле был интегрирован, я бы знал, что с ним случилось…
2. Итак, чтобы мне было ясно, у вас есть вектор кортежей типов с конструкторами с несколькими аргументами?
3. @MarkB: Не совсем. У меня есть один тип кортежа, и я хочу построить такой кортеж из аргументов конструкторов составляющих типов напрямую, без промежуточной инициализации копирования. Это часть общей идиомы-переход с C 03 на C 11, который по возможности предпочитает прямую инициализацию инициализации копирования.
4. в каком смысле std::массив является частным случаем std::кортежа? имеет ли кортеж std::непрерывные ограничения памяти?
5. @rhalbersma
std::array
поддерживает интерфейс «доступ, подобный кортежу», но на этом сходство заканчивается. Там также нет кусочной конструкции, и ее не может быть, поскольку она является совокупностью и не может иметь явных конструкторов.
Ответ №1:
Вопрос: Почему для массивов и кортежей не существует одинаковой кусочной конструктив-ности?
Насколько я помню, кусочная конструкция была добавлена std::pair
только по одной причине: для поддержки построения распределителя использования элементов пары, т. Е. для предоставления распределителя и условной передачи элементам, если они поддерживают построение с помощью распределителя (см. [распределитель.использует] в стандарте).
В какой-то момент в процессе C 0x std::pair
было вдвое больше конструкторов, чем сейчас, причем каждый конструктор имел соответствующую версию «расширенного распределителя», принимающую std::allocator_arg_t
аргумент и аргумент распределителя, например
template<class T, class U>
struct pair {
pair();
pair(allocator_arg_t, const Allocamp;);
template<class TT, class UU>
pair(TTamp;amp;, UUamp;amp;);
template<class Alloc, class TT, class UU>
pair(allocator_arg_t, const Allocamp;, TTamp;amp;, UUamp;amp;);
// etc.
Было что-то вроде ходячей шутки (ха-ха, только серьезной) о безумной сложности std::pair
. Поддержка передачи распределителей элементам была удалена std::pair
и перенесена в std::scoped_allocator_adaptor
, которая отвечает за определение того, следует ли создавать элементы с помощью распределителя (см. construct
Перегрузки, указывающие std::pair
на [распределитель.адаптер.элементы]).
Приятным следствием кусочной конструкции является то, что вы можете выполнить инициализацию парных элементов в стиле «emplace», позволяя создавать пары неподвижных, не копируемых типов, но, насколько я знаю, это не было целью дизайна.
Таким образом, причина tuple
, по которой это не поддерживается, заключается в том, что функция была изобретена для упрощения, pair
которая превратилась из очень простого типа в C 03 в посмешище в C 0x, но делать то же самое для tuple
не считалось важным (в любом случае это было ново для C 11). Кроме того, расширение scoped_allocator_adaptor
для обработки кортежей произвольного числа элементов значительно усложнило бы этот адаптер.
Что касается std::array
, это агрегатный тип (по причинам), поэтому добавление конструктора piecewise_construct_t
невозможно, не сделав его неагрегатным.
Комментарии:
1. Мне бы очень понадобилась кусочная конструкция для кортежей! Это полезно.
Ответ №2:
Я не знаю, почему его там нет. Ранее я думал, что реализация будет невозможна, учитывая текущий синтаксис шаблона varadic, но я понял, что это можно сделать, если разбить его на части.
Если бы они определили такой интерфейс, как этот:
template<typename... T>
tuple(piecewise_construct, Tamp;amp;... 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...>amp;amp; 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...>, ArgTupleamp;amp; 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() = defau<
// For non-piecewise constructors, forward them
template<typename... Args>
piecewise_tuple(Argsamp;amp;... args) : base_type(std::forward<Args>(args)...) {}
template<typename... T>
piecewise_tuple(std::piecewise_construct_t, Tamp;amp;... 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>, intamp; >
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;
}
Комментарии:
1. 1 Хотя я не совсем согласен с этой
array
частью, основной момент (почему для кортежа нет кусочного конструктора) хорош. В этомstd::array
случае amake_array
можно было бы предоставить без добавления конструкторовstd::array
, и реализация на самом деле была бы простой. Хотя я не думаю, что это принесло бы большую пользу.2. Я не вижу в этом проблемы:
template<class... Tuples> tuple(piecewise_construct_t, Tuplesamp;amp;...)
. Возможно, вы могли бы добавить черту, которая проверяет, действительно ли всеTuples
они одного типаstd::tuple
.3. @Xeo: Hrm… вы правы, и вы могли бы использовать static_assert для проверки длины аргументов. И вам даже не придется использовать только
std::tuple
(хотя это наиболее вероятное использование).4. Еще одним решением было бы разрешить определенное реализацией ограничение на количество аргументов, отличное от обычного ограничения длины пакета. Решите проблему с двумя вариантами так же, как проблема с одним вариантом была решена в C 03.
5. Для справки, одним из пунктов продажи
std::piecewise_construct
является то, что он позволяет создавать на месте неподвижные типы (когда для такого типа требуется более одного аргумента). Однако большая загвоздка в том, что существует только один способ реализации кусочной конструкции-делегирование конструкторов, что делает ее очень навязчивой. Кусочные конструкторы должны быть,std::tuple
чтобы получить настоящее благо.
Ответ №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, ValuesRefamp;amp; ... values)
: next( std::forward<ValuesRef>(values)... )
{}
template <std::size_t ...ids, class FieldValue, class ...ValuesRef>
TupleHolder(std::index_sequence<ids...>, FieldValueamp;amp; field, ValuesRefamp;amp; ... values)
:
field( std::get<ids>(std::forward<FieldValue>(field))... ),
next( std::forward<ValuesRef>(values)... )
{};
template <class FieldValue, class ...ValuesRef>
TupleHolder(FieldValueamp;amp; field, ValuesRefamp;amp; ... 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...>, FieldValueamp;amp; field)
:
field( std::get<ids>(std::forward<FieldValue>(field))... )
{}
template <class FieldValue>
TupleHolder(FieldValueamp;amp; 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(Tamp; data) noexcept{
return GetLoop<index 1, target_index, typename T::nextT>::get(
data.next
);
}
constexpr static decltype(auto) get(const Tamp; data) noexcept{
return GetLoop<index 1, target_index, typename T::nextT>::get(
data.next
);
}
constexpr static decltype(auto) get(Tamp;amp; 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 typeamp; get(Tamp; data) noexcept{
return data.field;
}
constexpr static const typeamp; get(const Tamp; data) noexcept{
return data.field;
}
constexpr static typeamp;amp; get(Tamp;amp; 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(TuplePiecewiseamp; other)
: TuplePiecewise(static_cast<const TuplePiecewiseamp;>(other)) {}
template <class ...ValuesRef>
explicit constexpr TuplePiecewise(ValuesRefamp;amp; ... values) noexcept
: data( std::forward<ValuesRef>(values)... ){}
TuplePiecewise( const TuplePiecewiseamp; other ) = defau<
TuplePiecewise( TuplePiecewiseamp;amp; other ) = defau<
static constexpr const std::size_t size = sizeof...(FieldTypes);
};
template<int index, class ...FieldTypes>
constexpr decltype(auto) get(TuplePiecewise<FieldTypes...> amp;amp;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...> amp;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...> amp;list) noexcept {
return GetLoop<0, index, typename TuplePiecewise<FieldTypes...>::fieldsT >::get( list.data );
}
Использование:
TuplePiecewise< CopyTest, intamp;, 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)));