Swapping two types in a type list
Asked Answered
S

3

6

For simplicity, let's use std::tuple as our type list.

What is the best (concise, least recursive, etc.) way to swap two types in a std::tuple?

Illustration of the functionality through the use of indices:

#include <tuple>

int main()
{
    using tuple_t = std::tuple<int, void, double>;          // int, void, double
    using swapped_tuple_t = std::tuple<double, void, int>;  // double, void, int
    static_assert( std::is_same<swap<0, 2, tuple_t>::type, swapped_tuple_t>::value, "!" );
}
Stent answered 22/11, 2015 at 20:3 Comment(0)
C
7
#include <tuple>
#include <utility>
#include <cstddef>

template <std::size_t I
        , std::size_t J
        , typename T
        , typename = std::make_index_sequence<I>
        , typename = std::make_index_sequence<J - I - 1>
        , typename = std::make_index_sequence<std::tuple_size<T>::value - J - 1>>
struct swap;

template <std::size_t I
        , std::size_t J
        , typename T
        , std::size_t... As
        , std::size_t... Bs
        , std::size_t... Cs>
struct swap<I, J, T
         , std::index_sequence<As...>
         , std::index_sequence<Bs...>
         , std::index_sequence<Cs...>
         >
{
    using type = std::tuple<typename std::tuple_element<As, T>::type...
                          , typename std::tuple_element<J, T>::type
                          , typename std::tuple_element<Bs + I + 1, T>::type...
                          , typename std::tuple_element<I, T>::type
                          , typename std::tuple_element<Cs + J + 1, T>::type...>;
};

DEMO


For cases where J can be lower than or equal to I, use the below trait:

template <std::size_t I, std::size_t J, typename T>
struct swap : swap_impl<I<J?I:J, I<J?J:I, T> {};

template <std::size_t I, typename T>
struct swap<I,I,T>
{
    using type = T;
};

DEMO 2

Cornelison answered 22/11, 2015 at 20:43 Comment(3)
Didn't know about std::make_index_sequence. Nice clean solution.Donnenfeld
@MarcoA. Good to point out issues, but it's not a big deal since swap<2, 0> can just be converted to swap<0, 2>.Stent
@Stent yes, the code before the edit failed for swap<x,y> with x>=y.. anyway I'm just wondering how come it doesn't happen with libc++Sagittarius
S
6

There's no reason to use three sequences. One is enough:

template <std::size_t I
        , std::size_t J
        , typename T
        , typename = std::make_index_sequence<std::tuple_size<T>::value>>
struct swap_impl;

template <std::size_t I
        , std::size_t J
        , typename T
        , std::size_t... As>
struct swap_impl<I, J, T
               , std::index_sequence<As...>
         >
{
    using type = std::tuple<std::tuple_element_t<As == I ? J : As == J? I : As, T>... >;
};

template <std::size_t I, std::size_t J, typename T>
struct swap : swap_impl<I, J, T> {};

And now there's also no need to special-case the I >= J case.

Demo.

Shadowgraph answered 22/11, 2015 at 21:52 Comment(1)
That's a nice technique, I'll definitely keep it in mind. My original solution was similar to Piotr's, but a little less elegant. In my opinion, this one clearer and obviously concise.Stent
D
3

I am throwing-in an alternative (but I think that @Piotr's solution to be more elegant).

template <size_t ...>
struct seq { };

// X, Y are the indeces we want to swap
template <size_t N, size_t X, size_t Y, size_t ...S>
struct gen : gen<N-1, X, Y, (N-1 == X ? Y : (N-1 == Y ? X : N - 1)), S...> { };

template <size_t X, size_t Y, size_t ...S>
struct gen<0, X, Y, S...> {
  typedef seq<S...> type;
};

// X and Y are the index we want to swap, T is the tuple
template <size_t X, size_t Y, class T, class S>
struct swapImpl;

template <size_t X, size_t Y, class T, size_t... S>
struct swapImpl<X, Y, T, seq<S...>>{
    using type = std::tuple<typename std::tuple_element<S, T>::type...>;
};

template <size_t X, size_t Y, class T>
struct swap {
    using type = typename swapImpl<X, Y, T, 
                         typename gen<std::tuple_size<T>::value, X, Y>::type>::type;
};

int main() {
  using tuple_t = std::tuple<int, unsigned, void, char, double>;          // int, void, double
  using swapped_tuple_a_t = std::tuple<unsigned, int, void, char, double>;  // double, void, int
  static_assert( std::is_same<swap<0, 1, tuple_t>::type, swapped_tuple_a_t>::value, "!" );
  static_assert( std::is_same<swap<1, 0, tuple_t>::type, swapped_tuple_a_t>::value, "!" );
  using swapped_tuple_b_t = std::tuple<int, char, void, unsigned, double>;  // double, void, int
  static_assert( std::is_same<swap<1, 3, tuple_t>::type, swapped_tuple_b_t>::value, "!" );
  static_assert( std::is_same<swap<3, 1, tuple_t>::type, swapped_tuple_b_t>::value, "!" );
}
Donnenfeld answered 22/11, 2015 at 21:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.