Suppose I have a tuple-like that is essentially a wrapper around std::tuple
(so a solution for std::tuple
works too). How would I go writing a constructor/tuple_cat equivalent that can take an arbitrary amount of arguments of the same templated type but with each having a subset of the template arguments of the return.
The problem with just using std::tuple_cat
is that the return type is based on arguments. What I would like to do is have a fixed return type, so that any member not passed in inside a tuple would be default-constructed. It also allows for duplicate types while I would like to error or any overlap in type arguments to the arguments. Ordering is preserved too
An example of what I mean, the constraints in comments are enforced by requires clauses
template<typename... Ts>
// Ts constrained to only one of each type
struct MyTuple {
template<typename... Tuples>
// Tuples constrained to all be instantiations of MyTuple
// each Tuples<Us...> constrained that Us is strictly a subset of types in Ts
// every pair of Tuples<Us...> constrained to not have any overlap in their Us
// const& used for simplicity to not get into std::forward-ing everything
MyTuple(Tuples const&... args)
{
// no clue
// using initializer list would be better, but even less of a clue on that
}
std::tuple<Ts...> mTuple;
}
Usage would be something like:
// A..E are default constructible, distinct types
using SuperTuple = MyTuple<A, B, C, D, E>;
// A, B, C and E are passed in as arguments, D is default-constructed
auto super = SuperTuple{MyTuple<A>{}, MyTuple<E,B>{}, MyTuple<C>{}};
Is this even possible?
Here's an implementation that
The difficulty in your requirements lie in how the std::tuple
constructor must have the correct arity. To remedy that, we create an "identity" type default_construct_t
that does the right thing when we're missing an argument, similar to how 0
is the identity for sums.
To avoid recursion or a large amount of metaprogramming trying to index into the pack of tuples, we conveniently reuse default_construct_t
to fold across types in std::common_type_t
.
#include<tuple>
#include<utility>
struct default_construct_t
{
using type = default_construct_t;
template<typename T>
operator T() { return T(); }
};
template<typename, typename>
struct has_element : std::false_type
{
};
template<typename... Args, typename T>
requires ((std::is_same_v<T, Args> || ...))
struct has_element<std::tuple<Args...>, T>
: std::true_type
{
};
template<typename Tup, typename T>
concept contains = has_element<std::decay_t<Tup>, T>::value;
template<typename T, typename... Tuples>
decltype(auto) find(Tuples&&... tuples)
{
using R = std::common_type_t<
std::conditional_t<
contains<decltype(tuples), T>,
std::type_identity<decltype(tuples)>,
default_construct_t
>...
>::type;
if constexpr(std::is_same_v<R, default_construct_t>)
return default_construct_t{};
else
return std::get<T>(std::get<R>(
std::forward_as_tuple(std::forward<Tuples>(tuples)...)
));
}
template<typename... Ts>
struct MyTuple
{
template<typename... Tuples>
MyTuple(Tuples&&... tuples)
: mTuple{find<Ts>(std::forward<Tuples>(tuples)...)...}
{
}
std::tuple<Ts...> mTuple;
};
Live.