从包中删除某个类型的某些(但不是全部)匹配项

Removing some (but not all) occurrences of a type from a pack

本文关键字:全部 删除 包中 类型      更新时间:2023-10-16
template <typename Pack, typename T, std::size_t... Is> struct remove_some

找到Is... T 的Pack中删除T。 例如:

template <typename...> struct P;   template <typename...> struct Q;
static_assert (std::is_same<
    remove_some<std::tuple<int, char, bool, int, int, double, int>, int, 1,2>,
    std::tuple<int, char, bool, double, int>
>::value, "");
static_assert (std::is_same<
    remove_some<std::tuple<int, char, bool, int, int, double, int>, int, 0,3>,
    std::tuple<char, bool, int, int, double>
>::value, "");
static_assert (std::is_same<
    remove_some<std::tuple<int, char, P<long, int, short>, bool, int, int, double, int>,
        int, 0,1,2,4>,
    std::tuple<char, P<long, short>, bool, int, double>
>::value, "");
这些断言

都通过我当前的代码传递,但问题是让这个断言传递:

static_assert (std::is_same<  // Fails
    remove_some<std::tuple<int, char, P<long, int, Q<int, int, int>, short>, bool, int, int, double, int>, int, 0,1,2>,
    std::tuple<char, P<long, Q<int, int>, short>, bool, int, int, double, int>
>::value, "");

即一包中的一包,我似乎无法确定失败的原因。 这是我到目前为止当前的代码,包括我认为错误的地方,但更好的方法总是受欢迎的。

#include <iostream>
#include <type_traits>
#include <utility>
#include <tuple>
template <typename Pack, typename T, std::size_t Count, typename Output, std::size_t... Is> struct remove_some_h;
template <typename Pack, typename T, std::size_t... Is>
using remove_some = typename remove_some_h<Pack, T, 0, std::tuple<>, Is...>::type;
template <template <typename...> class P, typename First, typename... Rest, typename T, std::size_t Count, typename... Output, std::size_t I, std::size_t... Is>
struct remove_some_h<P<First, Rest...>, T, Count, std::tuple<Output...>, I, Is...> : remove_some_h<P<Rest...>, T, Count, std::tuple<Output..., First>, I, Is...> {};
// T is found, but it is not the Ith one, so do NOT remove it.  Increase Count by 1 to handle the next T.
template <template <typename...> class P, typename... Rest, typename T, std::size_t Count, typename... Output, std::size_t I, std::size_t... Is>
struct remove_some_h<P<T, Rest...>, T, Count, std::tuple<Output...>, I, Is...> : remove_some_h<P<Rest...>, T, Count + 1, std::tuple<Output..., T>, I, Is...> {};
// T is found, and it is the next one to remove, so remove it and increase Count by 1 to handle the next T.
template <template <typename...> class P, typename... Rest, typename T, std::size_t Count, typename... Output, std::size_t... Is>
struct remove_some_h<P<T, Rest...>, T, Count, std::tuple<Output...>, Count, Is...> : remove_some_h<P<Rest...>, T, Count + 1, std::tuple<Output...>, Is...> {};
// No more indices left, so no more T's to remove and hence just adjoin Rest... to the output.
template <template <typename...> class P, typename... Rest, typename T, std::size_t Count, typename... Output>
struct remove_some_h<P<Rest...>, T, Count, std::tuple<Output...>> {
    using type = P<Output..., Rest...>;
    static constexpr std::size_t new_count = Count;
    using remaining_indices = std::index_sequence<>;
};
// No more types left to check, though there are still some T's left to remove (e.g. from an outerpack that contains this inner pack).
template <template <typename...> class P, typename T, std::size_t Count, typename... Output, std::size_t... Is>
struct remove_some_h<P<>, T, Count, std::tuple<Output...>, Is...> {
    using type = P<Output...>;
    static constexpr std::size_t new_count = Count;
    using remaining_indices = std::index_sequence<Is...>;
};
// No more types left to check, nor any T's left to remove (this is needed to avoid ambiguity).
template <template <typename...> class P, typename T, std::size_t Count, typename... Output>
struct remove_some_h<P<>, T, Count, std::tuple<Output...>> {
    using type = P<Output...>;
    static constexpr std::size_t new_count = Count;
    using remaining_indices = std::index_sequence<>;
};
// The problem case (dealing with inner packs):
template <typename Pack, typename T, std::size_t Count, typename Output, typename IndexSequence> struct remove_some_h_index_sequence;
template <typename Pack, typename T, std::size_t Count, typename Output, std::size_t... Is>
struct remove_some_h_index_sequence<Pack, T, Count, Output, std::index_sequence<Is...>> : remove_some_h<Pack, T, Count, Output, Is...> {};
template <template <typename...> class P, template <typename...> class Q, typename... Ts, typename... Rest, typename T, std::size_t Count, typename... Output, std::size_t I, std::size_t... Is>
struct remove_some_h<P<Q<Ts...>, Rest...>, T, Count, std::tuple<Output...>, I, Is...> {  // I is needed to avoid ambiguity.
    static constexpr std::size_t new_count = Count;  // I think this value is wrong?
    using remaining_indices = std::index_sequence<I, Is...>;  // I think this is the wrong sequence?
    using inner = remove_some_h<Q<Ts...>, T, Count, std::tuple<>, I, Is...>;  // Take care of the inner pack first.
    using type = typename remove_some_h_index_sequence<P<Rest...>, T, inner::new_count, std::tuple<Output..., typename inner::type>, typename inner::remaining_indices>::type;
};

你处理这个问题是错误的。 您的基元以深度优先的方式遍历类型树,计算给定类型的元素,并在某些索引处删除它们,这是一个荒谬的基元。 从某种意义上说,它做得太多了,也太具体了。

相反,您应该研究现有的列表处理函数式语言如何解决此类问题。 他们构建基元运算并撰写它们,而不是像你这样手写复杂的基元。

第一步是平面列表。 您希望遍历平面列表,对每个元素运行一个谓词。 如果谓词这样说,则表示要消除该元素。 您希望在此过程中修改谓词的状态。

接下来,将线性列表遍历器修改为树的深度优先遍历。 这可以通过谓词突变器(一个接受谓词,并在访问参数后使其进入参数内容(来完成。

因此,现在您正在对类型树进行深度优先遍历,通过从中删除类型的筛选器。

现在我们构建一个谓词,上面写着"删除T类型的第 n 个实例"。

--

这样做的要点是,这些技术中的每一种都可以单独进行测试。

两种可能有帮助的方法是一种将模板视为类型(模板是带有template<class...Ts> using result=/*...*/;的类型(,或者采用 hana 风格并在 constexpr 中进行元编程,并使用类型标记的 decltype'd 函数。

template<class T>struct tag_type{using type=T;};
template<class T>constexpr tag_type<T> tag{};

用于 hana 样式元编程的标记。

一些 hana 样式类型的筛选器:

struct filter_never {
  template<class U>
  constexpr std::pair<std::false_type, filter_never>
  operator()(tag_type<U>){ return {}; }
};
template<class T, std::size_t N>
struct filter_nth {
  template<class U>
  constexpr std::pair< std::false_type, filter_nth<T, N> >
  operator()(tag_type<U>)const{return {};}
  constexpr std::pair< std::false_type, filter_nth<T, N-1> >
  operator()(tag_type<T>)const{return {};}
};
template<class T>
struct filter_nth<T, 0> {
  template<class U>
  constexpr std::pair< std::false_type, filter_nth<T, 0> >
  operator()(tag_type<U>)const{return {};}
  constexpr std::pair< std::true_type, filter_never >
  operator()(tag_type<T>)const{return {};}
};

过滤器合并:

template<class...Filters>
struct filter_any:filter_never{};
template<class...>struct types_tag { using type=types_tag; };
template<class...Ts>
constexpr types_tag<Ts...> types{};
template<class...Truth, class...Filters>
constexpr auto merge_filter_results( std::pair<Truth, Filters>... )
-> std::pair<
    std::integral_constant<bool, (Truth{} || ...)>, // C++1z, can write but long in C++11
    filter_any<Filters...>
  >
{ return {}; }
template<class F0, class...Filters>
struct filter_any<F0, Filters...> {
  template<class U>
  constexpr auto operator()(tag_type<U> t)const {
    return merge_filter_results( F0{}(t), Filters{}(t)... );
  }
};

filter_any<Filters...>合并任意数量的筛选器,并将它们应用于每个元素。 如果有人说"丢弃",结果是丢弃。

所以,int, 1, 2变得filter_any<filter_nth<int, 1>, filter_nth<int, 2>>.

这看起来很复杂;但重要的是我只是将从列表中消除多个元素的问题简化为测试一次消除一个元素的能力,并测试filter_any。 两个组件,每个组件都经过单独测试。

template<class...T0s, class...T1s>
constexpr types_tag<T0s..., T1s...> concat_elements( types_tag<T0s...>, types_tag<T1s...> )
{ return {}; }
template<class Filter>
constexpr types_tag<> filter_elements( Filter, types_tag<> ) { return {}; }
template<class T0, class...Ts, class Filter>
auto filter_elements( Filter, types_tag<T0, Ts...> )
-> decltype(
  concat_elements( std::conditional_t<
      Filter{}(tag<T0>).first,
      types_tag<>,
      types_tag<T0>
    >{},
    filter_elements( Filter{}(tag<T0>).second, types_tag<Ts...> )
  )
)
{ return {}; }

现在,除了拼写错误,我们可以:

auto r = filter_elements(
  filter_any<filter_nth<int, 1>, filter_nth<int, 2>>{},
  types_tag<int, char, char, std::string, char, int, char, int, int, char>{}
);

现在r的类型是

types_tag<int, char, char, std::string, char, char, int, char>

剩下要做的就是调试上面的废话,并处理下降。

活生生的例子。

我研究types_tag,因为采用任何模板类型并将其来回转录到 types_tag 相对容易。 我们在轻量级类型(如types_tag(中所做的工作越多,我们的工作速度就越快。

我们所需要的只是一个转录:

template<template<class...>class Z, class types>
struct transcribe;
template<template<class...>class Z, class types>
using transcribe_t=typename transcribe<Z,types>::type;
template<template<class...>class Z, class...Ts>
struct transcribe<Z,types_tag<Ts...>>:tag_type<Z<Ts...>> {};

并窃取:

template<class T>
struct as_types;
template<class T>
using as_types_t=typename as_types<T>::type;
template<template<class...>class Z, class...Ts>
struct as_types<Z<Ts...>>:types_tag<Ts...>{};

从任意包中来回移动。

template<class T, class Filter>
struct filter_elements_out;
template<class T, class Filter>
using filter_elements_out_t=typename filter_elements_out<T,Filter>::type;
template<template<class...>class Z, class...Ts, class Filter>
struct filter_elements_out:
  type_tag<
    transcribe_t<Z,
      decltype(filter_elements(Filter{},types<Ts...>{}))
    >
  >
{};

这很容易,不是吗?

我确实谈到了适应深度优先过滤器。 这需要更多的努力。

事实证明,让函数返回 types_tag<...> 而不是 true/false 并连接回原始列表效果更好。 (当我实现它时注意到了这一点(。

下面是一个过滤器>polymap(返回集合的映射(和一个深度优先适配器:

template<class Filter>
struct filter_to_polymap {
  template<class U>
  constexpr
  std::pair<
    std::conditional_t<
      Filter{}(tag<U>).first,
      types_tag<>,
      types_tag<U>
    >,
    filter_to_polymap<decltype(Filter{}(tag<U>).second)>
  > operator()(tag_type<U>)const {
    static_assert(
      !std::is_same<decltype(Filter{}(tag<U>).second), filter_never>{}
      ,""
    );
    return {};
  }
};
template<class Polymap>
constexpr std::pair<types_tag<>, Polymap> map_elements_ex( Polymap, types_tag<> ) { return {}; }
template<class Polymap, class T0, class...Ts>
constexpr auto map_elements_ex( Polymap p, types_tag<T0, Ts...> )
{
  return std::make_pair(
  concat_elements( 
    p(tag<T0>).first,
    map_elements_ex( p(tag<T0>).second, types<Ts...> ).first
  ),
  map_elements_ex( p(tag<T0>).second, types<Ts...> ).second
  );
}
template<class Polymap, class...Ts>
auto map_elements( Polymap p, types_tag<Ts...> ) {
  return map_elements_ex(p, types<Ts...>).first;
}
template<class Polymap, template<class...>class Z, class...Ts>
auto map_elements_tagged( Polymap, tag_type<Z<Ts...>> ) {
  return tag< transcribe_t<Z, decltype(map_elements(Polymap{}, types<Ts...>))> >;
}
template<class Polymap>
struct depth_first_polymap {
  template<template<class...>class Z, class...Ts>
  constexpr auto operator()(tag_type<Z<Ts...>>) const {
    auto r = map_elements_ex(*this, types<Ts...>);
    return std::make_pair(
      types<transcribe_t<Z, decltype(r.first)>>,
      r.second
    );
  }
  template<class U>
  constexpr auto operator()(tag_type<U>) const {
    auto r=Polymap{}( tag<U> );
    return std::make_pair(
      r.first,
      depth_first_polymap<decltype(r.second)>{}
    );
  }
};

这使得解决您的问题变得容易。

以下是对最后一个remove_some_h子句的修复:

template <template <typename...> class P, template <typename...> class Q,
          typename... Ts, typename... Rest, typename T, std::size_t Count,
          typename... Output, std::size_t I, std::size_t... Is>
struct remove_some_h<P<Q<Ts...>, Rest...>, T, Count,
                     std::tuple<Output...>, I, Is...> {  // I is needed to avoid ambiguity.
    using inner = remove_some_h<Q<Ts...>, T, Count, std::tuple<>, I, Is...>;  // Take care of the inner pack first.
    using removed = remove_some_h_index_sequence<
        P<Rest...>, T, inner::new_count, std::tuple<Output...,
                                                    typename inner::type>,
        typename inner::remaining_indices>;
    using type = typename removed::type;
    static constexpr auto new_count = removed::new_count;
    using remaining_indices = typename removed::remaining_indices;
};