3

I am attempting to convert between two types of std::tuples but I'm having trouble getting the implementation right. I want it to map if the type is the same, but it needs to allow duplicate types to get mapped in the original order.

The basic logic is:

while has pair<input, output>
 if type(input) == type(output) 
   then do map, next input, next output
 else
   next input
assert(all outputs mapped)

With an example mapping being:

Sample Remapping

My "best" attempt at a solution looks like this:

template <auto> struct value {};
template <auto... Vals> struct value_sequence {};
template <class... Vals> struct placeholder {};

template <auto... As, auto... Bs>
constexpr value_sequence<As..., Bs...> operator+(value_sequence<As...>, value_sequence<Bs...>)
{
    return {};
}

template <size_t Idx, size_t... Idxs, size_t OtherIdx, size_t... OtherIdxs, class T, class... Ts, class OtherT, class... OtherTs>
constexpr auto mapper(const std::index_sequence<Idx, Idxs...>&, const std::index_sequence<OtherIdx, OtherIdxs...>&, const placeholder<T, Ts...>&,
                      const placeholder<OtherT, OtherTs...>&)
{
    if constexpr (sizeof...(OtherIdxs) == 0)
    {
        static_assert(std::is_same_v<T, OtherT>);
        return value_sequence<Idx>{};
    }
    else if constexpr (std::is_same_v<T, OtherT>)
    {
        return value_sequence<Idx>{} +
               mapper(std::index_sequence<Idxs...>{}, std::index_sequence<OtherIdxs...>{}, placeholder<Ts...>{}, placeholder<OtherTs...>{});
    }
    else
    {
        return mapper(std::index_sequence<Idx, Idxs...>{}, std::index_sequence<OtherIdxs...>{}, placeholder<T, Ts...>{}, placeholder<OtherTs...>{});
    }
}

Called with:

mapper(std::make_index_sequence<sizeof...(Ts)>{}, std::make_index_sequence<sizeof...(OtherTs)>{},
                                                placeholder<Ts...>{}, placeholder<OtherTs...>{})

Which gives the compiler error error C2672: 'mapper': no matching overloaded function found pointing to the else if case of the function mapper

I'm working in c++17, and it looks like all the bits I need are there, I just can't assemble them the right way.

Any help would be really appreciated!

bpmckinnon
  • 125
  • 4

3 Answers3

4

Here's a fairly simple recursive implementation:

// pop_front implementation taken from https://stackoverflow.com/a/39101723/4151599
template <typename Tuple, std::size_t... Is>
auto pop_front_impl(const Tuple& tuple, std::index_sequence<Is...>)
{
    return std::make_tuple(std::get<1 + Is>(tuple)...);
}

template <typename Tuple>
auto pop_front(const Tuple& tuple)
{
    return pop_front_impl(tuple,
                          std::make_index_sequence<std::tuple_size<Tuple>::value - 1>());
}

template <typename...>
std::tuple<> map_tuple(std::tuple<>)
{
    return {};
}

template <typename FirstOutput, typename... Outputs,
          typename FirstInput, typename... Inputs>
std::tuple<FirstOutput, Outputs...>
map_tuple(const std::tuple<FirstInput, Inputs...>& input)
{
    if constexpr (std::is_same_v<FirstInput, FirstOutput>) {
        return std::tuple_cat(
            std::tuple<FirstOutput>(std::get<0>(input)),
            map_tuple<Outputs...>(pop_front(input))
        );
    } else {
        return map_tuple<FirstOutput, Outputs...>(pop_front(input));
    }
}

Simply call it like

std::tuple<int, double, char, int, int, float> tup = {1, 2.0, '3', 4, 5, 6.0};
auto mapped = map_tuple<int, double, char, float>(tup);

Demo

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
  • It works! I had to make a small change so it would work if there was only one output argument. http://coliru.stacked-crooked.com/a/c2b3444f1beb99d9 – bpmckinnon Feb 10 '23 at 20:48
  • One more small note. I have one case where I have a tuple of reference_wrapper, which make_tuple converts to a tuple of T&. To work around this I needed to make my own make_local_tuple function. http://coliru.stacked-crooked.com/a/caa00b9c4f597063 – bpmckinnon Feb 11 '23 at 23:17
  • And I didn't really like the decay, so I replaced make_local_tuple with std::tuple...>. http://coliru.stacked-crooked.com/a/d392e9e4032875e3 – bpmckinnon Feb 11 '23 at 23:26
4

To begin, need a helper template to peel off the first type from a tuple.

#include <utility>
#include <type_traits>
#include <tuple>
#include <cstdlib>
#include <iostream>

template<typename T>
struct peel_tuple_t;

template<typename T, typename ...Args>
struct peel_tuple_t< std::tuple<T, Args...>> {

    typedef std::tuple<Args...> type_t;
};

template<typename T>
using peel_tuple=typename peel_tuple_t<T>::type_t;

static_assert(std::is_same_v< peel_tuple<std::tuple<int, float>>,
          std::tuple<float>>);

That's simple enough: std::tuple<int, float> -> std::tuple<float>.

Next, we need an equivalent of tuple_cat, but for index_sequences:

template<typename T, typename T2> struct index_sequence_cat_t;

template<size_t ...A, size_t ...B>
struct index_sequence_cat_t<std::index_sequence<A...>,
                std::index_sequence<B...>> {
    typedef std::index_sequence<A..., B...> type_t;
};

template<typename T, typename T2>
using index_sequence_cat=typename index_sequence_cat_t<T, T2>::type_t;

static_assert(std::is_same_v<index_sequence_cat<
          std::index_sequence<1, 2>,
          std::index_sequence<3, 4>
          >, std::index_sequence<1, 2, 3, 4>>);

We can now use this to take a std::tuple<float, int, double, double>>, which provides the values, then a std::tuple<float, double, double>, where the values go, and produce a std::index_sequence<0, 2, 3> that will tell us to std::get index 0, 2, and 3 from the source tuple into the destination tuple:

template<typename output_tuple, typename input_tuple, size_t index>
struct map_input_tuple_t : map_input_tuple_t<output_tuple,
                         peel_tuple<input_tuple>,
                     index+1> {};

template<typename T, typename ...Args2, size_t index>
struct map_input_tuple_t<std::tuple<T>,
             std::tuple<T, Args2...>, index> {

    typedef std::index_sequence<index> type_t;
};

template<typename T, typename ...Args, typename ...Args2, size_t index>
struct map_input_tuple_t<std::tuple<T, Args...>,
             std::tuple<T, Args2...>, index> {

    typedef index_sequence_cat<
        std::index_sequence<index>,
        typename map_input_tuple_t<std::tuple<Args...>,
                       std::tuple<Args2...>,
                       index+1>::type_t> type_t;
};

template<typename output_tuple, typename input_tuple>
using map_input_tuple=
    typename map_input_tuple_t<output_tuple, input_tuple, 0>::type_t;

static_assert(std::is_same_v< map_input_tuple<
          std::tuple<float, double, double>,
          std::tuple<float, int, double, double>>,

          std::index_sequence<0, 2, 3>>);

And now, this is a solved problem:

template<typename mapping> struct map_tuple_impl;

template<size_t ...n>
struct map_tuple_impl<std::index_sequence<n...>> {

    template<typename T>
    static auto doit(const T &t)
    {
        return std::tuple{ std::get<n>(t)... };
    }
};

template<typename output_tuple, typename ...input_types>
auto map_tuple(const std::tuple<input_types...> &input)
{
    return map_tuple_impl<map_input_tuple<output_tuple,
                          std::tuple<input_types...>>>
        ::doit(input);
}

int main()
{
    auto t=map_tuple<std::tuple<float, double, double>
             >(std::tuple{1.0f, 2,
                      2.0,
                      3.0});

    static_assert(std::is_same_v<decltype(t),
              std::tuple<float, double, double>>);

    if (t == std::tuple{1.0F, 2.0, 3.0})
        std::cout << "yes\n";
    return 0;
}
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
3

Is too late to play?

I propose the following struct to select the matching indexes

template <typename, typename, std::size_t...>
struct get_match_ind;

template <typename A0, typename ... As, typename ... Bs,
          std::size_t I0, std::size_t... Is>
struct get_match_ind<std::tuple<A0, As...>, std::tuple<A0, Bs...>, I0, Is...>
 : public get_match_ind<std::tuple<As...>, std::tuple<Bs...>, I0+1u, Is..., I0>
{ };

template <typename A0, typename ... As, typename ... Bs,
          std::size_t I0, std::size_t... Is>
struct get_match_ind<std::tuple<A0, As...>, std::tuple<Bs...>, I0, Is...>
 : public get_match_ind<std::tuple<As...>, std::tuple<Bs...>, I0+1u, Is...>
{ };
                        
template <typename ... As, std::size_t I0, std::size_t... Is>
struct get_match_ind<std::tuple<As...>, std::tuple<>, I0, Is...>
{ using type = std::index_sequence<Is...>; };

template <typename T1, typename T2>
using match_indexes = typename get_match_ind<T1, T2, 0u>::type;

Now a map_tuple() function with a simple helper

template <typename Tb, typename Ta, std::size_t ... Is>
auto map_tuple_helper (Ta const & t0, std::index_sequence<Is...> const &)
{ return Tb{ std::get<Is>(t0)... }; }

template <typename Tb, typename Ta>
auto map_tuple (Ta const & t0)
{ return map_tuple_helper<Tb>(t0, match_indexes<Ta, Tb>{}); }

The following is a full compiling C++17 example with a static_assert() to verify the mapping

#include <tuple>

template <typename, typename, std::size_t...>
struct get_match_ind;

template <typename A0, typename ... As, typename ... Bs,
          std::size_t I0, std::size_t... Is>
struct get_match_ind<std::tuple<A0, As...>, std::tuple<A0, Bs...>, I0, Is...>
 : public get_match_ind<std::tuple<As...>, std::tuple<Bs...>, I0+1u, Is..., I0>
{ };

template <typename A0, typename ... As, typename ... Bs,
          std::size_t I0, std::size_t... Is>
struct get_match_ind<std::tuple<A0, As...>, std::tuple<Bs...>, I0, Is...>
 : public get_match_ind<std::tuple<As...>, std::tuple<Bs...>, I0+1u, Is...>
{ };
                        
template <typename ... As, std::size_t I0, std::size_t... Is>
struct get_match_ind<std::tuple<As...>, std::tuple<>, I0, Is...>
{ using type = std::index_sequence<Is...>; };

template <typename T1, typename T2>
using match_indexes = typename get_match_ind<T1, T2, 0u>::type;

template <typename Tb, typename Ta, std::size_t ... Is>
auto map_tuple_helper (Ta const & t0, std::index_sequence<Is...> const &)
{ return Tb{ std::get<Is>(t0)... }; }

template <typename Tb, typename Ta>
auto map_tuple (Ta const & t0)
{ return map_tuple_helper<Tb>(t0, match_indexes<Ta, Tb>{}); }


int main()
{
  // target type
  using TT = std::tuple<int, double, char, float>;

  std::tuple<int, double, char, int, int, float> t0 {1, 2.0, '3', 4, 5, 6.0};

  auto t1 { map_tuple<TT>(t0) };

  using T1 = decltype(t1);

  static_assert( std::is_same_v<TT, T1> );
}
max66
  • 65,235
  • 10
  • 71
  • 111