43

The standard templates std::pair and std::array are special cases of std::tuple, and it stands to reason that they should have a very similar set of capabilities.

However, uniquely among the three, std::pair allows for piecewise construction. That is, if the types T1 and T2 can be constructed from a set of arguments a1, a2, ... and b1, b2, ..., then morally speaking we can make a pair

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

directly. Practically, this is spelt out as something like this:

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

Question: Why doesn't the same piecewise constructibility exist for arrays and tuples? Is there a profound reason, or is this a plain omission? For example, it would be nice to have:

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, ...));

Is there a reason this cannot be done? [Edit: Or am I misunderstanding the purpose of piecewise construction entirely?]

(I do really have a situation in which I would like to initialize a vector of tuples with a defaulted element value which I would prefer to construct directly from the arguments, without spelling out each tuple element type again.)

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 2
    It's there in N3059 and it is gone again in N3140. Now, if a human could just find out which report was actually integrated, I would know what happened to it... – pmr Aug 07 '12 at 13:15
  • So just so I'm clear, you have a vector of tuples of types with multi-argument constructors? – Mark B Aug 07 '12 at 13:16
  • 1
    @MarkB: Not quite. I have a single tuple type, and I want to construct such a tuple from a the arguments of the constructors of the constituent types directly, without intermediate copy-initialization. This is part of the general idiom-shift from C++03 to C++11 which prefers direct-initialization over copy-initialization whenever possible. – Kerrek SB Aug 07 '12 at 13:24
  • 1
    in what sense is std::array a special case of std::tuple? does std::tuple have contiguous memory constraints? – TemplateRex Aug 07 '12 at 13:29
  • @rhalbersma `std::array` supports the "tuple-like access" interface but there the similarity ends. There is also no piecewise construction there, nor can there be as it's an aggregate and can't have explicit constructors. – Potatoswatter Aug 07 '12 at 13:31
  • The best I can come up with is passing n `boost::in_place`'s into `emplace_back`. – Mark B Aug 07 '12 at 13:36
  • @Potatoswatter: Hm, maybe my example isn't good then? Do you have a better example of how to use piecewise construction? Me, I mainly want to avoid having to say the type names repeatedly. – Kerrek SB Aug 07 '12 at 13:41
  • @rhalbersma: They're similar in terms of interface: they have `tuple_size` and `element_type` traits; they have iterators, and `get` accessors. It's true that `array` has additional details (contiguous memory, aggregate, and `[]`-accessor), though. – Kerrek SB Aug 07 '12 at 13:47
  • @pmr [N3140](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3140.html) focuses on reference members of pairs and tuples. But as far as I can tell, `std::pair` also supports reference members, and will misbehave, binding a member reference to a temporary when you try to piecewise construct a const lvalue or rvalue reference member. EDIT: Maybe that's not a problem, since the user might want that when the resulting tuple is also a temporary. – Potatoswatter Aug 07 '12 at 13:47
  • @Potatoswatter: Thanks for your edit -- I only noticed now that you installed `forward_as_tuple`s. I never used that one before! :-) – Kerrek SB Aug 07 '12 at 13:57
  • @rhalbersma: Another similarity: There are type-deducing maker functions `std::make_pair` and `std::make_tuple`, as well as the much-missed [`make_array`](http://stackoverflow.com/a/6272491/596781) that should really exist... – Kerrek SB Aug 07 '12 at 14:05
  • Where did you get the first syntax from? I am not seeing a variadic template constructor in pair that takes a single sequence of arguments. The one present in the standard is the second one, taking a tag and two tuples. I have the feeling that your premise does not really hold – David Rodríguez - dribeas Aug 07 '12 at 14:08
  • The only guess I have is the shear uglyness that would result in the signature, since you would need tuple_size argument packs. In theory, you might be able to have it accept a variable number of arguments itself, and do something to delegate it. As for `std::array`, it has no constructors, and defining one would prevent it from being an aggregate. – Dave S Aug 07 '12 at 14:09
  • @DaveS: You actually have a point... such a constructor would require a variable number of variadic template argument packs. A variadic variadic template if you may, for which there is no appropriate syntax. You should write that as an answer. On the other hand, having a `make_array` does not require `array` to have constructors --I don't think `make_array` is needed, but the lack of constructors would not be a reason not to have `make_array` – David Rodríguez - dribeas Aug 07 '12 at 14:13
  • @DavidRodríguez-dribeas I agree with `make_array`, but I was more thinking about a piecewise_construct constructor for `std::array` – Dave S Aug 07 '12 at 14:20
  • @DavidRodríguez-dribeas: The first piece of code is pseudo-code in quotation marks. I agree that `array` can't get a constructor, but conversely, the `make_` functions don't make sense with piecewise arguments, since the whole point of them is to *deduce* the element type. `make` and `piecewise` are mutually exclusive concepts. – Kerrek SB Aug 07 '12 at 14:22
  • "morally speaking" ?? – M.M Aug 24 '15 at 22:38

3 Answers3

19

Question: Why doesn't the same piecewise constructibility exist for arrays and tuples?

My recollection is that piecewise construction was added to std::pair for one reason only: to support uses-allocator construction of the pair elements, i.e. to allow an allocator to be provided and conditionally passed to the elements if they support construction with an allocator (see [allocator.uses] in the standard).

At one point during the C++0x process std::pair had twice as many constructors as it does now, with every constructor having a corresponding "allocator-extended" version taking a std::allocator_arg_t and an allocator argument e.g.

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.

There was something of a running joke (haha, only serious) about the insane complexity of std::pair. The support for passing allocators to the elements was removed from std::pair and moved into std::scoped_allocator_adaptor, which is responsible for detecting whether the elements should be constructed with an allocator (see the construct overloads taking a pointer to std::pair in [allocator.adaptor.members]).

A nice consequence of the piecewise construction is that you can do "emplace" style initialization of pair elements, allowing pairs of non-movable, non-copyable types, but as far as I know that was not the goal of the design.

So the reason tuple doesn't support it is that the feature was invented to simplify pair which had ballooned from a very simple type in C++03 to a laughing stock in C++0x, but doing the same for tuple was not considered as important (it was new for C++11 anyway). Also, extending scoped_allocator_adaptor to handle tuples of arbitrary numbers of elements would have made that adaptor much more complicated.

As for std::array, that's an aggregate type (because reasons) so adding a constructor taking piecewise_construct_t is not possible without making it a non-aggregate.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
13

I'm not sure why it's not there. Previously, I thought that the implementation wouldn't be possible, given the current varadic template syntax, but I realized that it can be done if it's broken into pieces.

If they had defined an interface like this:

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

And made it a requirement that the arguments are something that you can use std::get<N> to access the arguments (basically, tuples, pairs, arrays). There would have to be extra checks to verify there isn't a mismatch between the number of arguments given and the number of elements in the tuple.

Edit: This problem has been bothering me since I read it. And I've created the following class, it is derived from std::tuple, and has no data members, so you can assign it to the tuple and the slicing is harmless. The current version requires that the elements be moveable or copyable, as it creates a temporary and then inserts that into the tuple. If you were a tuple implementer, it should be possible to eliminate even that move.

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;
}
Dave S
  • 20,507
  • 3
  • 48
  • 68
  • +1 Although I don't quite agree on the `array` part, the main point (why is there no piecewise constructor for tuple) is good. On the `std::array` case, a `make_array` could be provided without adding constructors to `std::array`, and the implementation would actually be simple. Although I don't think it would provide much value. – David Rodríguez - dribeas Aug 07 '12 at 14:19
  • 1
    I don't see this as a problem: `template tuple(piecewise_construct_t, Tuples&&...)`. You could possibly add a trait that checks if all `Tuples` are really of type `std::tuple`. – Xeo Aug 07 '12 at 14:24
  • @Xeo: Hrm... you're right, and you could use a static_assert to verify the length of the arguments. And you wouldn't even have to use only `std::tuple` (though that's the most likely use). – Dave S Aug 07 '12 at 14:27
  • One other solution would be to allow an implementation-defined limit to the number of arguments separate from the usual pack length limit. Solve the double-variadic problem the same way the single-variadic problem was solved in C++03. – Potatoswatter Aug 07 '12 at 15:01
  • @Xeo Sounds like a job for a Concept. Maybe the mysterious removal was related to that major feature drop? – Potatoswatter Aug 07 '12 at 15:03
  • @Potatoswatter: I don't think so. A concept would be neat, but in fact you don't even need the trait. When you try to access the "tuple", you get an error anyways if it doesn't support the tuple interface. – Xeo Aug 07 '12 at 15:20
  • 1
    For the record one of the selling points of `std::piecewise_construct` is that it allows in-place construction of non-movable types (when it takes more than one argument for such a type). The big catch however is that there's only one way to implement piecewise construction, via delegating constructors -- which makes it very intrusive. Piecewise constructors must be in `std::tuple` to get the real boon. – Luc Danton Aug 07 '12 at 17:38
  • I have heard this claim before that piecewise construction requires delegating constructors, but you can also implement it with a base class that has the constructor you need (essentially delegating constructors, but without the language feature). – David Stone Feb 15 '16 at 22:56
1

Here is my implementation of tuple piecewise (it also allow to omit values with omit "keyword"). Zero overhead (no copy/move - direct construction):

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 );
}

Usage:

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;

Tuple inside tuple (zero overhead):

TuplePiecewise< string, TuplePiecewise<int,int> > list4(forward_as_tuple("RRR"), forward_as_tuple(forward_as_tuple(10), forward_as_tuple(20)));
tower120
  • 5,007
  • 6
  • 40
  • 88