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

4

20

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.

Isom answered 2/5, 2022 at 10:27 Comment(5)
Are we talking about a full compile time solution?Digestif
@Digestif yes, this would be executed in constexpr contextIsom
why not auto extract = tuple; to make a copy ?Nicodemus
@463035818_is_not_a_number He wants an extract, not a copy...Digestif
oh new I get it. extract should equal {1.2,2.3,4.5f} after extracing all double and floats from tupleNicodemus
F
16

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;
}
Fictile answered 2/5, 2022 at 11:22 Comment(0)
P
11

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.

Pears answered 2/5, 2022 at 13:38 Comment(0)
J
7

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<>{});
Jardine answered 2/5, 2022 at 11:29 Comment(0)
B
5

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});
}
Becker answered 2/5, 2022 at 17:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.