Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equivalent of std::transform for tuples

I want a function that will behave like std::transform for tuples. Basically the functionality to be implemented is

template<size_t From, size_t To, class Tuple, class Func>
void tuple_transform(Tuple&& source, Tuple&& target, Func f)
{
    // elements <From, To> of `target` ti become `f(si)`, where
    // si is the corresponding element of `source`
};

I believe that to implement this I'll need a compile time integer range struct, a generalization of std::index_sequence and I've implemented it here with cti::range. I also believe that this type of compile time traversal is ideal here :

template<class Func, class Tuple, size_t...Is>
void for_each_in_tuple(Func f, Tuple&& tuple, std::index_sequence<Is...>){
    using expander = int[];
    (void)expander { 0, ((void)f(std::get<Is>(std::forward<Tuple>(tuple))), 0)... };
}

template<class Func, class Tuple>
void for_each_in_tuple(Func f, Tuple&& tuple){
    for_each_in_tuple(f, std::forward<Tuple>(tuple),
               std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>());
}

Can anyone help me with the implementation ?


Notes : On the type of the mutation function

@MohitJain Like in the code linked (tuple_transform) this is not taken into account (Func has a single type). If this is solved like that, I could easily extend it by passing a template template parameter template<class> class Func and mandate that my transformation types are like this

template<typename T>
struct Func
{
    static void apply(T& val) { ... }
}

then inside the body of tuple transform, each transformation func could be called like :

get<I>(target) = func<typename tuple_element<I, Tuple>::type>::apply(get<I>(source))

EDIT

Just gave a lightning talk @ accu 2015. The above was the CodeKata at the end of the presentation. I'll leave here the presentation, hopefullly it'll help with any implementation attempts (I think pretty much every tool required is presented, so we'll have more attempts at this)

like image 385
Nikos Athanasiou Avatar asked Apr 23 '15 10:04

Nikos Athanasiou


2 Answers

Here's a solution that uses index_range from here.

template<size_t SN, size_t DN, class TSrc, class TDest, class Func>
void tuple_call_assign(TSrc&& source, TDest& target, Func f)
{
    std::get<DN>(target) = f(std::get<SN>(std::forward<TSrc>(source))); 
}

template<size_t From, size_t To, class TSrc, class TDest, class Func, size_t...Is, size_t...DIs>
void tuple_transform(TSrc&& source, TDest& target, Func f,
                     std::index_sequence<Is...>, std::index_sequence<DIs...>)
{
    using expander = int[];
    (void)expander { 0, (tuple_call_assign<Is,DIs>(std::forward<TSrc>(source),target,f), 0)... };
}

template<size_t From, size_t To, size_t FromDest, class TSrc, class TDest, class Func>
void tuple_transform(TSrc&& source, TDest& target, Func f)
{
    static_assert(To > From, "Range must be increasing");
    static_assert(To <= std::tuple_size<std::decay_t<TSrc>>::value+1, 
        "Range must be valid for source tuple");
    constexpr size_t RangeSize = To-From;
    static_assert(FromDest+RangeSize <= std::tuple_size<std::decay_t<TDest>>::value, 
        "Range must be valid for target tuple");

    tuple_transform<From,To>(std::forward<TSrc>(source), target, f,
                    index_range<From,To>(), index_range<FromDest, FromDest+RangeSize>());
}

Demo

This takes a third template argument to specify a starting index to transform into the target tuple.

like image 64
TartanLlama Avatar answered Oct 01 '22 23:10

TartanLlama


The index_sequence solution has been mentioned already:

template <std::size_t From, size_t... indices, typename T1, typename T2, typename Func>
void transform(T1&& s, T2& t, Func f, std::index_sequence<indices...>)
{
    (void)std::initializer_list<int>{
        (std::get<indices+From>(t) = f(std::get<indices>(std::forward<T1>(s))), 0)...};  
}


template <std::size_t From, std::size_t To, typename T1, typename T2, typename Func>
void transform(T1&& s, T2& t, Func f)
{
    transform<From>(std::forward<T1>(s), t, f, std::make_index_sequence<To-From+1>());
}

Demo.

like image 32
Columbo Avatar answered Oct 01 '22 22:10

Columbo