Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remapping a tuple onto another tuple

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!

like image 890
bpmckinnon Avatar asked May 21 '26 22:05

bpmckinnon


2 Answers

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

like image 189
Miles Budnek Avatar answered May 24 '26 18:05

Miles Budnek


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;
}
like image 38
Sam Varshavchik Avatar answered May 24 '26 19:05

Sam Varshavchik



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!