Piecewise Tuple
Table of Contents
Motivation #
Why a piecewise tuple? It may be that some of the types that in our tuple do not have a default constructor, or perhaps share some sort of common constructor and we want to initialise them all the same way.
In addition, std::pair
which is really just a tuple of two types, has a piecewise constructor.
Requirements #
We have a few requirements:
- the existing piecewise constructor for
std::pair
uses tag-dispatch, we will use the same idea. - reuse existing standard library functions, such as
std::get<>
to access the tuple
Getting started #
We want to write this:
piecewise_tuple<Foo, Bar, Void> t { std::piecewise_construct,
std::forward_as_tuple{fooArg1, fooArg2},
std::forward_as_tuple{barArg1, barArg2},
std::tuple{} };
We will inherit from std::tuple
, and create a new constructor similar to that of std::pair
:
template <typename... Elements>
class piecewise_tuple : public std::tuple<Elements...> {
using base_tuple = std::tuple<Elements...>;
public:
template <typename... TupleArgs>
explicit constexpr piecewise_tuple(std::piecewise_construct_t, TupleArgs&&... args) { /*elided*/ }
// elided
};
How do we proceed from here?
Tuples all the way down. #
A tuple is a list of types. So let’s use more tuples. We need to pass those args
so let’s make a tuple
of tuples
. And since we’re going to need to unpack the tuple, we will pass an integer sequence to assist with look up and unpacking:
template <typename... TupleArgs>
explicit constexpr piecewise_tuple(std::piecewise_construct_t, TupleArgs&&... args)
: piecewise_tuple(
std::make_index_sequence<sizeof...(TupleArgs)>{},
std::forward_as_tuple(std::forward<TupleArgs>(args)...)
) {
static_assert(sizeof...(TupleArgs) == sizeof...(Elements),
"Number of tuple arguments should equal number of tuple elements");
}
Delegate #
Now we delegate to another constructor:
template <std::size_t... N, typename ArgTuple>
explicit constexpr piecewise_tuple(std::index_sequence<N...>, ArgTuple&& argTuple)
: base_tuple(
construct<N>(
std::get<N>(std::forward<ArgTuple>(argTuple)),
std::make_index_sequence<
std::tuple_size_v<
std::decay_t<std::tuple_element_t<N, std::decay_t<ArgTuple>>>
>
>{}
)...
) {
}
and use pack expansion to “iterate” over the (piecewise) tuple, using the index sequence we passed in, and construct each type in the piecewise tuple.
One small wrinkle is that we need to generate another index sequence for the arguments used to construct the type.
Since we’re using perfect fowarding that preserves any qualifiers, we need to decay
or remove_reference
on the type, and thus prevent 'error: incomplete type ... used in nested name specifier'
in gcc.
Construction #
Since the arguments for each type in the piecewise tuple are themselves in a tuple, we’ll need to unpack that using std::get<N>(tuple)
:
template <std::size_t Index, typename... Args, std::size_t... N>
static constexpr auto construct(std::tuple<Args...>&& args, std::index_sequence<N...> /*unused*/ ) {
// Look up the Index-th type in the piecewise tuple
using result_type = std::tuple_element_t<Index, base_tuple>;
// finally, pass the args into the type (of the tuple element) for construction
// pack expansion of the argument tuple
return result_type(std::get<N>(std::move(args))...); // Construct the type!!!!
}
This is notionally doing the equivalent of:
auto foo = Foo(fooArg1, fooArg2);
return foo;
And we’re done #
We now have a piecewise tuple that can be constructed from the given set of arguments.
Where do we go from here? We can, using tag-dispatch, add another constructor to the piecewise tuple that can pass one set of arguments to all types in the tuple if they share the same constructor arguments.
Full version is here.