Home > Back-end >  How to extract all tuple elements of given type(s) into new tuple
How to extract all tuple elements of given type(s) into new tuple

Time:05-02

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.

CodePudding user response:

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.

CodePudding user response:

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

CodePudding user response:

The idea of this 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 indices of the type in the tuple, for float, it is 5, for double, it is 3, 4, then extract the elements according to the indices and construct a tuple with an only single type, finally, just use tuple_cat to concatenate them together

#include <array>
#include <tuple>
#include <utility>

template<typename T, typename... Args>
constexpr auto extract_tuple_of(const std::tuple<Args...>& t) {
  constexpr auto indices = []<std::size_t... Is>
  (std::index_sequence<Is...>) {
    std::array<bool, sizeof...(Args)> find{
      std::is_same_v<std::tuple_element_t<Is, std::tuple<Args...>>, T>...
    };
    std::array<std::size_t, sizeof...(Args)> 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::index_sequence_for<Args...>{});

  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& tuple) {
  return std::tuple_cat(extract_tuple_of<Ts_out>(tuple)...);
}

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<>{});
  • Related