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 tuple
s 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>{});
}
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 insert
s 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.