Search code examples
c++template-meta-programmingstdtuple

Merging two tuples according to a given criteria


Suppose that we have two tuples a and b.

I want to implement a function merge_tuples that creates a new tuple c that is a merge of a and b according to a provided binary mask: if a bit is set, we take one item of a, otherwise we take one item of b.

Note that the returned tuple should reference the items of a and b (i.e. we don't want to copy objects here).

Example:

#include <tuple>
#include <cassert>

template<unsigned long long MASK, typename...A, typename...B>
auto merge_tuples (std::tuple<A...>& a, std::tuple<B...>& b)
{
    // In the example below, we kwow that we have to return the following:
    return std::tie
    (
        std::get<0> (a),
        std::get<0> (b),
        std::get<1> (a),
        std::get<1> (b),
        std::get<2> (b)
    );
    
    // BUT HOW TO DO IT GENERICALLY BY USING 'MASK' ?
}

int main ()
{
    std::tuple<float,int>        a {float {3.1},   int{2}          };
    std::tuple<double,int,char>  b {double{1.123}, int{4}, char{9} };

    auto c = merge_tuples <0b00101> (a,b);
    
    assert (std::get<0>(c) == float {3.1}  );   // mask[0]==1  => take item 0 from a
    assert (std::get<1>(c) == double{1.123});   // mask[1]==0  => take item 0 from b
    assert (std::get<2>(c) == int   {2}    );   // mask[2]==1  => take item 1 from a
    assert (std::get<3>(c) == int   {4}    );   // mask[3]==0  => take item 1 from b
    assert (std::get<4>(c) == char  {9}    );   // mask[4]==0  => take item 2 from b
}

How can such a merge_tuples function be implemented?


Solution

  • std::index_sequence might help:

    template <std::size_t N>
    constexpr std::array<std::pair<std::size_t, std::size_t>, N>
    mask_to_array(std::uint64_t mask)
    {
        std::array<std::pair<std::size_t, std::size_t>, N> res{};
        std::size_t ai = 0;
        std::size_t bi = 0;
    
        for (auto& [ab, i] : res) {
            ab = mask & 1;
            i = ab ? ai++ : bi++;
            mask >>= 1; 
        }
        return res;
    }
    
    template <std::uint64_t MASK, typename...A, typename...B>
    constexpr auto merge_tuples (std::tuple<A...>& a, std::tuple<B...>& b)
    {
        constexpr std::size_t N = sizeof...(A) + sizeof...(B);
        constexpr auto arr = mask_to_array<N>(MASK);
    
        return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
            return std::tie(std::get<arr[Is].second>(std::get<arr[Is].first>(std::tie(b, a)))...);
    
        }(std::make_index_sequence<N>());
    }
    

    Demo