Velvet Star Monitor

Standout celebrity highlights with iconic style.

news

How to extract all tuple elements of given type(s) into new tuple

Writer 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.

5

4 Answers

Only C++17 is needed here.

std::tuple_cat is one of my favorite tools.

  1. Use a std::index_sequence to chew through the tuple

  2. Use a specialization to pick up either a std::tuple<> or a std::tuple<T> out of the original tuple, for each indexed element.

  3. Use std::tuple_cat to glue everything together.

  4. 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)...);
}

Demo

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});
}

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy