【问题标题】:Removing some (but not all) occurrences of a type from a pack从包中删除某些(但不是全部)出现的类型
【发布时间】:2016-05-17 16:45:08
【问题描述】:

template <typename Pack, typename T, std::size_t... Is> struct remove_somePack 中删除T,这是找到的Is... 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;
};

【问题讨论】:

  • 第一个提示:失败案例实际生成的类型是std::tuple&lt;char, P&lt;long, Q&lt;int, int&gt;, short&gt;, bool, int, double, int&gt;。缺少一个int
  • “这些断言都通过了我当前的代码,但问题是让这个断言通过”——要求断言通过是原始的疯狂。 remove_somethat 远不是人们所看到的最不意外的原则。我怀疑当你想做一些那个奇怪的事情时,你需要编写不同的原语,并组合它们来完成你的任务。
  • @Yakk。我不认为修复是那么激烈。 md51 指出了一个简单的修复方法,但是当深入一个内包时,他的修复似乎仍然失败。也许最终的解决方案与他的解决方案并没有太大的不同。另外,如果我们只是从左到右读取计数,我看不出这是多么疯狂。

标签: c++ templates c++14 variadic-templates


【解决方案1】:

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

相反,您应该研究现有的列表处理函数式语言如何解决此类问题。他们构建原始操作并组合它们,而不是像您这样手写复杂的原始操作。

第一步是平面列表。您想遍历一个平面列表,在每个元素上运行一个谓词。如果谓词是这样说的,那么您想消除该元素。您想在进行过程中修改谓词的状态。

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

所以现在您正在对类型树进行深度优先遍历,并通过一个过滤器来消除其中的类型。

现在我们构建了一个谓词“删除类型 T 的第 n 个实例”。

--

关键是这些技术中的每一个都可以独立测试。

两种可能有帮助的方法是将模板视为类型(模板是其中包含 template&lt;class...Ts&gt; 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&lt;Filters...&gt; 合并任意数量的过滤器,并将它们应用于每个元素。如果有人说“丢弃”,结果就是丢弃。

所以,int, 1, 2 变成了filter_any&lt;filter_nth&lt;int, 1&gt;, filter_nth&lt;int, 2&gt;&gt;

这看起来很复杂;但重要的部分是我只是将消除列表中的多个元素的问题简化为测试一次消除一个元素的能力,并测试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>

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

live example.

我在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&lt;...&gt; 而不是 true/false,然后将 concat 返回到原始列表效果会更好。 (我在实施时注意到了这一点)。

这是一个过滤器->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)>{}
    );
  }
};

这使它成为easy to solve your problem

【讨论】:

  • @Yakk 我接受你的回答不仅因为它是一种全新的方法,而且因为我喜欢这样对我苛刻。但是,如果您查看其他响应,他对我的方法的修复(我测试它可以正常工作,但从未想过)非常简单,不是吗?
  • @prestokeys 呵呵,当然:无论如何,现在完成了答案。这是第一次 - 将其清理为更纯粹的 hana 风格,或更纯粹的模板风格,并删除在开发过程中引入的一些冗余应该会让事情更快乐。
【解决方案2】:

这是您最后一个 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;
};

【讨论】:

  • @ md51 好的,这修复了失败的静态断言。但现在这个断言失败了:static_assert (std::is_same&lt; remove_some&lt;std::tuple&lt;int, char, P&lt;long, int, Q&lt;int, int, P&lt;int, bool&gt;, int&gt;, short&gt;, bool, int, int, double, int&gt;, int, 0,1,4,5,8&gt;, std::tuple&lt;char, P&lt;long, Q&lt;int, int, P&lt;bool&gt;&gt;, short&gt;, bool, int, int, double&gt; &gt;::value, ""); 当深入一个嵌套包时,失败似乎仍然会蔓延。
  • @prestokeys 应该被修复。
  • @ md51 是的,它有效。我也用更深的嵌套对其进行了测试。我会接受你的回复,但我相信 Yakk 你正在修复一个错误的方法(我的方法)。
猜你喜欢
  • 2021-12-26
  • 1970-01-01
  • 1970-01-01
  • 2019-02-05
  • 1970-01-01
  • 2021-10-06
  • 1970-01-01
  • 2017-06-02
  • 1970-01-01
相关资源
最近更新 更多