Home > database >  Concatenate entries of tuples
Concatenate entries of tuples

Time:07-15

I have two tuples like this

std::tuple<std::vector<int>, std::vector<int>> t1; 
std::tuple<std::vector<int>, std::vector<int>> t2; 

I now want to concat the entries of the tuples (so that I have one tuple containing two vectors with the entries of the the first/second vectors of the tuples). It is fine if the tuple is mutated.

I can do this like this:

std::get<0>(t1).insert(std::get<0>(t1).end(), std::get<0>(t2).begin(), std::get<0>(t2).end());

for each entry, but if i have a lot if entries in the tuple, it becomes very ugly.

Iterating the tuple with a normal for loop does not work since std::get requires a constant. I did not get it to work with std::apply because only one argument can be passed.

CodePudding user response:

Use nested fold-expression, one to expand the tuples and one to expand the elements of the tuple

#include <tuple>
#include <vector>

template<class FirstTuple, class... RestTuples>
void concatenate(FirstTuple& first, const RestTuples&... rest) {
  constexpr auto N = std::tuple_size_v<FirstTuple>;
  static_assert(((N == std::tuple_size_v<RestTuples>) && ...));
  [&]<std::size_t... Is>(std::index_sequence<Is...>) {
    ([&](const auto& other) { 
      (std::get<Is>(first).insert(
        std::get<Is>(first).end(),
        std::get<Is>(other).begin(),
        std::get<Is>(other).end()), ...);
    }(rest), ...);
  }(std::make_index_sequence<N>{});
}

Demo

CodePudding user response:

For a more concise answer, see what 康桓瑋 suggests.

In case you don't have access to C 20's templated lambda expressions, here is the code that uses C 17's features.

First, I would like to produce a new tuple that will consist of merged entries, that is:

auto t1 = std::tuple<std::vector<int>, std::vector<int>>{
        {1, 2, 3}, {7, 8}
};
auto t2 = std::tuple<std::vector<int>, std::vector<int>>{
        {4, 5, 6}, {9}
};

auto merged = merge_inner(t1, t2);

Thus, merge_inner needs to accept any number of said tuples:

template <
        template <typename> typename Tuple,
        template <typename> typename... Tuples,
        typename... Vectors
>
auto merge_inner(Tuple<Vectors...> first, Tuples<Vectors...> const&... args) {
    constexpr auto number_of_vectors = sizeof...(Vectors);

    append_values_elementwise(first, std::make_index_sequence<number_of_vectors>(), args...);
    return first;
}

We copy the first argument and take the rest by const&. To all vectors (elements) of first, we append those which are inside args... by calling append_values_elementwise. I used std::index_sequence in order to help fold-expressions making my life a little easier:

template <
        template <typename> typename Tuple,
        std::size_t... Ns,
        template <typename> typename... Tuples,
        typename... Vectors
>
auto append_values_elementwise(
        Tuple<Vectors...>& first,
        std::index_sequence<Ns...> sequence,
        Tuple<Vectors...> const& first_arg,
        Tuples<Vectors...> const&... args
) {
    ((std::get<Ns>(first).insert(
            std::get<Ns>(first).end(),
            std::get<Ns>(first_arg).begin(), std::get<Ns>(first_arg).end()
    )), ...);
    append_values_elementwise(first, sequence, args...);
}

This is just an implementation detail that extracts the next tuple from args... and inserts the elements of its vectors into first. sequence represents a meta-list of indexes suitable to be used with std::get. This is a metaprogramming way of looping over tuples values.

We then recurse with the rest of the arguments.

Lastly, we need a fallback to when we only have one tuple left to merge. The terminal case, which no longer recurses:

template <
        template <typename> typename Tuple,
        std::size_t... Ns,
        template <typename> typename... Tuples,
        typename... Vectors
>
auto append_values_elementwise(
        Tuple<Vectors...>& first,
        std::index_sequence<Ns...>,
        Tuple<Vectors...> const& first_arg
) {
    ((std::get<Ns>(first).insert(
            std::get<Ns>(first).end(),
            std::get<Ns>(first_arg).begin(), std::get<Ns>(first_arg).end()
    )), ...);
}

Demo.

  • Related