Reversing a Tuple
Table of Contents
Reversing a tuple #
I’m not sure how much use this method actually is, but it’s a useful exercise for learning template programming.
By type #
Suppose we have a type declaration:
using SomeTuple = std::tuple<Foo, Bar, Baz>;
then we want to be able to reverse it, and obtain a tuple that has the types in reverse order.
using ReversedTuple = reverse_tuple_t<SomeTuple>;
Let’s start with the definition:
template<typename... Ts>
struct reverse_tuple;
then we want to specialise and handle the empty case:
template <>
struct reverse_tuple<std::tuple<>> {
using type = std::tuple<>;
};
Next:
- we capture the first template argument, the
head
, as a type, and the rest as a pack, i.e.template<typename T, typename... Ts>
, - the tail, is the reverse tuple of the remaining types.
- we concatenate, using
std::tuple_cat
, thehead
to thetail
. - if we have more than one type, the first template argument is the head that we need to append to the next tail, which takes us back to the first point.
That’s recursion, and tells us we need to define the tail
type recursively.
As we have defined the empty case (above), we have the termination ‘clause’ for the recursion.
So we finally have:
template<typename... Ts>
struct reverse_tuple;
template <>
struct reverse_tuple<std::tuple<>> {
using type = std::tuple<>;
};
template <typename T, typename... Ts>
struct reverse_tuple<std::tuple<T, Ts...>> {
using head = std::tuple<T>;
using tail = typename reverse_tuple<std::tuple<Ts...>>::type; // we've popped off the head
using type = decltype(std::tuple_cat(std::declval<tail>(), std::declval<head>()));
};
and we’re done, other than a helper alias:
template<typename... Ts>
using reverse_tuple_t = reverse_tuple<Ts...>::type;
By value #
There may be cases where you want to reverse an existing tuple value.
using SomeTuple = std::tuple<Foo, Bar, Baz>;
SomeTuple t1;
auto t2 = reversed(t1);
As we mentioned in the
previous article, we can leverage integer sequences to make things a lot simpler. Using the standard technique of putting the implementation in a detail::
namespace, we start with:
template<typename T>
auto reversed(T&& t) {
return detail::reversed(std::forward<T>(t),
std::make_index_sequence<std::tuple_size_v<std::decay_t<T>>>{});
}
The implementation, is just a case of manipulating indexes to flip the ordering, i.e.
int n = 10;
for (int i = 0 ; i < n ; ++i) { std::cout << (n - 1 - i) << '\n'; }
// 9,8,7,6,5,4,3,2,1,0
We then use pack expansion ( ...
), and std::make_tuple
to build the reversed tuple.
Note that sizeof...(I)
is the size of all integer template parameters, i.e. notionally equivalent to n
above.
namespace detail {
template <typename T, std::size_t... I>
auto reversed(T&& t, std::index_sequence<I...>) {
return std::make_tuple(std::get<sizeof...(I) - 1 - I>(std::forward<T>(t))...);
}
}
template<typename T>
auto reversed(T&& t) {
return detail::reversed(std::forward<T>(t),
std::make_index_sequence<std::tuple_size_v<std::decay_t<T>>>{});
}
We could finally deduce the type of the reversed tuple, if needed by using decltype
.