如何将给定类型的所有元组元素提取到新元组中
How to extract all tuple elements of given type(s) into new tuple
std::get
的现有元组重载仅限于 return 按索引或类型恰好 1 个元素。想象一下,有一个包含多个相同类型元素的元组,并且您想将它们全部提取到一个新元组中。
如何实现 std::get<T>
的版本 return 是 std::tuple
所有给定类型的出现?
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}
不确定通过 std::get<index>
和 std::is_same_v
访问每个元素的 std::make_index_sequence
是否可行。
这里只需要C++17。
std::tuple_cat
是我最喜欢的工具之一。
用一个std::index_sequence
来咀嚼元组
使用特化从原始元组中为每个索引元素选择 std::tuple<>
或 std::tuple<T>
。
使用std::tuple_cat
将所有内容粘合在一起。
唯一棘手的部分是检查是否需要每个元组元素。为此,将所有需要的类型放入它自己的 std::tuple
中,并为该部分也使用一个助手 class。
#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;
}
实现思路如下(虽然boost.Mp11可能只需要几行)
以extract_from_tuple<float, double>
为例,其中tuple
是tuple<int, char, char, double, double, float>
,对于每一种类型,我们可以先计算其在tuple
中对应的索引,即5
for float
and 3, 4
for double
, 然后根据索引提取元素并构造一个相同类型的 tuple
最后使用 tuple_cat
将它们连接在一起
#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<>{});
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.
如果我们想使用 tuple_cat
,一个简洁的版本:
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.
另一个解决方案(最短?),经过检查,基本上是巴里的,但没有“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));
}
这是肯定的结果——但是请注意,排序来自元组而不是模板参数列表:
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});
}
std::get
的现有元组重载仅限于 return 按索引或类型恰好 1 个元素。想象一下,有一个包含多个相同类型元素的元组,并且您想将它们全部提取到一个新元组中。
如何实现 std::get<T>
的版本 return 是 std::tuple
所有给定类型的出现?
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}
不确定通过 std::get<index>
和 std::is_same_v
访问每个元素的 std::make_index_sequence
是否可行。
这里只需要C++17。
std::tuple_cat
是我最喜欢的工具之一。
用一个
std::index_sequence
来咀嚼元组使用特化从原始元组中为每个索引元素选择
std::tuple<>
或std::tuple<T>
。使用
std::tuple_cat
将所有内容粘合在一起。唯一棘手的部分是检查是否需要每个元组元素。为此,将所有需要的类型放入它自己的
std::tuple
中,并为该部分也使用一个助手 class。
#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;
}
实现思路如下(虽然boost.Mp11可能只需要几行)
以extract_from_tuple<float, double>
为例,其中tuple
是tuple<int, char, char, double, double, float>
,对于每一种类型,我们可以先计算其在tuple
中对应的索引,即5
for float
and 3, 4
for double
, 然后根据索引提取元素并构造一个相同类型的 tuple
最后使用 tuple_cat
将它们连接在一起
#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<>{});
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.
如果我们想使用 tuple_cat
,一个简洁的版本:
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.
另一个解决方案(最短?),经过检查,基本上是巴里的,但没有“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));
}
这是肯定的结果——但是请注意,排序来自元组而不是模板参数列表:
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});
}