Почему нет кусочно-кортежной конструкции?

#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 случае a make_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)));