How to extract all tuple elements of given type(s) into new tuple
Matthew Martinez
The existing tuple overloads of std::get are limited to return exactly 1 element by index, or type. Imagine having a tuple with multiple elements of the same type and you want to extract all of them into a new tuple.
How to achieve a version of std::get<T> that returns a std::tuple of all occurrences of given type(s) like this?
template<typename... Ts_out>
constexpr std::tuple<Ts_out...> extract_from_tuple(auto& tuple) { // fails in case of multiple occurences of a type in tuple return std::tuple<Ts_out...> {std::get<Ts_out>(tuple)...};
}
auto tuple = std::make_tuple(1, 2, 3, 'a', 'b', 'c', 1.2, 2.3, 4.5f);
auto extract = extract_from_tuple <float, double>(tuple);
// expecting extract == std::tuple<float, double, double>{4.5f, 1.2, 2.3}Not sure if std::make_index_sequence for accessing each element by std::get<index> and std::is_same_v per element could work.
4 Answers
Only C++17 is needed here.
std::tuple_cat is one of my favorite tools.
Use a
std::index_sequenceto chew through the tupleUse a specialization to pick up either a
std::tuple<>or astd::tuple<T>out of the original tuple, for each indexed element.Use
std::tuple_catto glue everything together.The only tricky part is checking if each tuple element is wanted. To do that, put all the wanted types into its own
std::tuple, and use a helper class for that part, too.
#include <utility>
#include <tuple>
#include <iostream>
// Answer one simple question: here's a type, and a tuple. Tell me
// if the type is one of the tuples types. If so, I want it.
template<typename wanted_type, typename T> struct is_wanted_type;
template<typename wanted_type, typename ...Types>
struct is_wanted_type<wanted_type, std::tuple<Types...>> { static constexpr bool wanted=(std::is_same_v<wanted_type, Types> || ...);
};
// Ok, the ith index in the tuple, here's its std::tuple_element type.
// And wanted_element_t is a tuple of all types we want to extract.
//
// Based on which way the wind blows we'll produce either a std::tuple<>
// or a std::tuple<tuple_element_t>.
template<size_t i, typename tuple_element_t, typename wanted_element_t, bool wanted=is_wanted_type<tuple_element_t, wanted_element_t>::wanted>
struct extract_type { template<typename tuple_type> static auto do_extract_type(const tuple_type &t) { return std::tuple<>{}; }
};
template<size_t i, typename tuple_element_t, typename wanted_element_t>
struct extract_type<i, tuple_element_t, wanted_element_t, true> { template<typename tuple_type> static auto do_extract_type(const tuple_type &t) { return std::tuple<tuple_element_t>{std::get<i>(t)}; }
};
// And now, a simple fold expression to pull out all wanted types
// and tuple-cat them together.
template<typename wanted_element_t, typename tuple_type, size_t ...i>
auto get_type_t(const tuple_type &t, std::index_sequence<i...>)
{ return std::tuple_cat( extract_type<i, typename std::tuple_element<i, tuple_type>::type, wanted_element_t>::do_extract_type(t)... );
}
template<typename ...wanted_element_t, typename ...types>
auto get_type(const std::tuple<types...> &t)
{ return get_type_t<std::tuple<wanted_element_t...>>( t, std::make_index_sequence<sizeof...(types)>());
}
int main()
{ std::tuple<int, const char *, double> t{1, "alpha", 2.5}; std::tuple<double, int> u=get_type<int, double>(t); std::cout << std::get<0>(u) << " " << std::get<1>(u) << std::endl; std::tuple<int, int, int, char, char, char, double, double, float> tt; auto uu=get_type<float, double>(tt); static_assert(std::is_same_v<decltype(uu), std::tuple<double, double, float>>); return 0;
} 0 With Boost.Mp11:
template <typename... Ts, typename Tuple>
auto extract_from_tuple(Tuple src) { // the indices [0, 1, 2, ..., N-1] using Indices = mp_iota<mp_size<Tuple>>; // the predicate I -> Tuple[I]'s type is one of {Ts...} using P = mp_bind< mp_contains, mp_list<Ts...>, mp_bind<mp_at, Tuple, _1>>; // the indices that satisfy P using Chosen = mp_filter_q<P, Indices>; // now gather all the appropriate elements return [&]<class... I>(mp_list<I...>){ return std::tuple(std::get<I::value>(src)...); }(Chosen{});
}Demo.
If we want to use tuple_cat, a concise version of that:
template <typename... Ts, typename Tuple>
constexpr auto extract_from_tuple2(Tuple src) { auto single_elem = []<class T>(T e){ if constexpr (mp_contains<mp_list<Ts...>, T>::value) { return std::tuple<T>(e); } else { return std::tuple<>(); } }; return std::apply([&](auto... e){ return std::tuple_cat(single_elem(e)...); }, src);
}Demo.
The idea of implementation is as follows (although boost.Mp11 may only need a few lines).
Take extract_from_tuple<float, double>, where tuple is tuple<int, char, char, double, double, float> as an example, for each type, we can first calculate its corresponding indices in the tuple, which is 5 for float and 3, 4 for double, then extract elements base on the indices and construct a tuple with same types, finally use tuple_cat to concatenate them together
#include <array>
#include <tuple>
#include <utility>
template<typename T, class Tuple>
constexpr auto
extract_tuple_of(const Tuple& t) { constexpr auto N = std::tuple_size_v<Tuple>; constexpr auto indices = []<std::size_t... Is> (std::index_sequence<Is...>) { std::array<bool, N> find{ std::is_same_v<std::tuple_element_t<Is, Tuple>, T>... }; std::array<std::size_t, find.size()> indices{}; std::size_t size{}; for (std::size_t i = 0, j = 0; j < find.size(); j++) { size += find[j]; if (find[j]) indices[i++] = j; } return std::pair{indices, size}; }(std::make_index_sequence<N>{}); return [&]<std::size_t... Is>(std::index_sequence<Is...>) { return std::tuple(std::get<indices.first[Is]>(t)...); }(std::make_index_sequence<indices.second>{});
};
template<typename... Ts_out, class Tuple>
constexpr auto
extract_from_tuple(const Tuple& t) { return std::tuple_cat(extract_tuple_of<Ts_out>(t)...);
}constexpr auto tuple = std::make_tuple(1, 2, 3, 'a', 'b', 'c', 1.2, 2.3, 4.5f);
constexpr auto extract1 = extract_from_tuple<float, double>(tuple);
constexpr auto extract2 = extract_from_tuple<int>(tuple);
constexpr auto extract3 = extract_from_tuple<long>(tuple);
static_assert(extract1 == std::tuple<float, double, double>{4.5f, 1.2, 2.3});
static_assert(extract2 == std::tuple<int, int, int>{1, 2, 3});
static_assert(extract3 == std::tuple<>{}); Yet another solution (the shortest?), which, after inspection, is basically Barry's but without the "mp_contains":
template<typename ... Ts>
constexpr auto extract_from_tuple(auto tuple)
{ auto get_element = [](auto el) { if constexpr ((std::is_same_v<decltype(el), Ts> || ...)) { return std::make_tuple(std::move(el)); } else { return std::make_tuple(); } }; return std::apply([&](auto ... args){ return std::tuple_cat(get_element(std::move(args)) ...);}, std::move(tuple));
}Here is the positive result -- note however that the ordering comes from the tuple and not the template argument list:
int main()
{ static_assert( extract_from_tuple <float, double>(std::make_tuple(1, 2, 3, 'a', 'b', 'c', 1.2, 2.3, 4.5f)) == std::tuple<double, double, float>{1.2, 2.3, 4.5f});
}