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 <typename T>
struct is_tuple : public std::false_type {};
template <typename... Ts>
struct is_tuple<std::tuple<Ts...>> : public std::true_type {}; |

template <typename T>
struct is_tuple : public std::false_type {};
template <typename... Ts>
struct is_tuple<std::tuple<Ts...>> : 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 <typename U, typename T, std::size_t ...Is>
auto tuple_cons(U&& u, T&& t, std::index_sequence<Is...>,
std::enable_if_t<is_tuple<std::decay_t<T>>::value>* = nullptr)
{
return std::make_tuple(std::forward<U>(u),
std::get<Is>(std::forward<T>(t))...);
}
template <typename U, typename T>
auto tuple_cons(U&& u, T&& t,
std::enable_if_t<is_tuple<std::decay_t<T>>::value>* = nullptr)
{
using Tuple = std::decay_t<T>;
return tuple_cons(std::forward<U>(u), std::forward<T>(t),
std::make_index_sequence<std::tuple_size<Tuple>::value>());
} |

template <typename U, typename T, std::size_t ...Is>
auto tuple_cons(U&& u, T&& t, std::index_sequence<Is...>,
std::enable_if_t<is_tuple<std::decay_t<T>>::value>* = nullptr)
{
return std::make_tuple(std::forward<U>(u),
std::get<Is>(std::forward<T>(t))...);
}
template <typename U, typename T>
auto tuple_cons(U&& u, T&& t,
std::enable_if_t<is_tuple<std::decay_t<T>>::value>* = nullptr)
{
using Tuple = std::decay_t<T>;
return tuple_cons(std::forward<U>(u), std::forward<T>(t),
std::make_index_sequence<std::tuple_size<Tuple>::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 <typename T, std::size_t ...Is>
auto tuple_tail(T&& t, std::index_sequence<Is...>,
std::enable_if_t<is_tuple<std::decay_t<T>>::value>* = nullptr)
{
return std::make_tuple(std::get<Is + 1>(std::forward<T>(t))...);
}
template <typename T>
auto tuple_tail(T&& t,
std::enable_if_t<is_tuple<std::decay_t<T>>::value>* = nullptr)
{
using Tuple = std::decay_t<T>;
return tuple_tail(std::forward<T>(t),
std::make_index_sequence<std::tuple_size<Tuple>::value - 1>());
} |

template <typename T, std::size_t ...Is>
auto tuple_tail(T&& t, std::index_sequence<Is...>,
std::enable_if_t<is_tuple<std::decay_t<T>>::value>* = nullptr)
{
return std::make_tuple(std::get<Is + 1>(std::forward<T>(t))...);
}
template <typename T>
auto tuple_tail(T&& t,
std::enable_if_t<is_tuple<std::decay_t<T>>::value>* = nullptr)
{
using Tuple = std::decay_t<T>;
return tuple_tail(std::forward<T>(t),
std::make_index_sequence<std::tuple_size<Tuple>::value - 1>());
}