Подтвердить что ты не робот

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

Стандартные шаблоны 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 снова.)

4b9b3361

Ответ 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)));