C++ provides a strange mix of compile-time and runtime functionality for dealing with tuples. There are some interesting parts, like std::tie
to destructure a tuple, and std::tuple_cat
to join together several tuples into one. So there is evidence that the standard has been influenced by some functional programming ideas, but I don’t think the full power of tuples has been realized (in both senses), and I found myself thinking about some missing parts.
Like why do we have std::tuple_cat
and std::get<0>
(aka std::tuple_head
), but not std::tuple_cons
or std::tuple_tail
? So here are some possible implementations.
First, something missing from type_traits
which will help us constrain things:
template
struct is_tuple : public std::false_type {};
template
struct is_tuple> : public std::true_type {};
Both tuple_cons
and tuple_tail
can make use of an expansion of std::index_sequence
to work their magic, with a user-facing function that provides the appropriate std::index_sequence
to an overload.
For tuple_cons
, we just call std::make_tuple
with the new element and the result of expanding std::get
across the other tuple:
template
auto tuple_cons(U&& u, T&& t, std::index_sequence,
std::enable_if_t>::value>* = nullptr)
{
return std::make_tuple(std::forward(u),
std::get(std::forward(t))...);
}
template
auto tuple_cons(U&& u, T&& t,
std::enable_if_t>::value>* = nullptr)
{
using Tuple = std::decay_t;
return tuple_cons(std::forward(u), std::forward(t),
std::make_index_sequence::value>());
}
And for tuple_tail
, we construct a std::index_sequence
of length n-1, then offset it by one in the expansion to obtain the tail:
template
auto tuple_tail(T&& t, std::index_sequence,
std::enable_if_t>::value>* = nullptr)
{
return std::make_tuple(std::get(std::forward(t))...);
}
template
auto tuple_tail(T&& t,
std::enable_if_t>::value>* = nullptr)
{
using Tuple = std::decay_t;
return tuple_tail(std::forward(t),
std::make_index_sequence::value - 1>());
}