递归模板参数删除

时间:2017-10-28 01:02:05

标签: c++ c++11 variadic-templates c++17 template-meta-programming

这个看似奇怪的问题是为了解决一种我似乎无法弄清楚的一般类型的递归。 remove_unit_packs<Pack>::type应为Pack,其中的所有单位包都被删除,其中单位包被定义为P<T>形式的任何包(包中只有一个元素,尽管该元素可以本身也是一个包。)因此

 remove_unit_packs< std::tuple<int, P<float>, char> >::type

 std::tuple<int, char>

这是我的实现,它使用递归:

#include <type_traits>
#include <tuple>

template <typename T>
struct is_unit_pack : std::false_type { };

template <template <typename> class P, typename T>
struct is_unit_pack<P<T>> : std::true_type { };

template <typename T, typename Output = std::tuple<>>
struct remove_unit_packs { using type = T; };

template <template <typename...> class P, typename... Output>
struct remove_unit_packs<P<>, std::tuple<Output...>> {
    using type = P<Output...>;
};

template <template <typename...> class P, typename First, typename... Rest, typename... Output>
struct remove_unit_packs<P<First, Rest...>, std::tuple<Output...>> : std::conditional_t<is_unit_pack<First>::value,
    remove_unit_packs<P<Rest...>, std::tuple<Output...>>,
    remove_unit_packs<P<Rest...>, std::tuple<Output..., typename remove_unit_packs<First>::type>>  // We use 'typename remove_unit_packs<First>::type>' instead of simply 'First' in case 'First' contains a unit pack despite not being a unit pack (in which case all unit packs must be removed from within 'First').
> { };

// Test
template <typename...> struct P;

int main() {
    static_assert(std::is_same<
        remove_unit_packs< std::tuple<int, P<float>, char> >::type,
        std::tuple<int, char>
    >::value);
    static_assert(std::is_same<
        remove_unit_packs< std::tuple<int, P<float, P<P<P<void>>, bool, int>, long>, char> >::type,
        std::tuple<int, P<float, P<bool, int>, long>, char>
    >::value);
}

另请注意,

是另一个例子
 remove_unit_packs< std::tuple<int, P<float, P<bool>>, char> >::type

 std::tuple<int, P<float>, char>

但是这个输出类型还包含一个包(P<float>),我也希望将其删除,即继续删除所有单元包,直到根本没有任何单元包(特殊情况除外)如果原包装已经成为一个单元包,在这种情况下保持不变)。但我上面的代码并没有这样做,因为它只执行一次删除操作。我可以通过两次通过来完成工作,但是会有一些情况需要3个,或者谁知道有多少次通过才能完全删除所有单元包。但我一直在搞清楚如何做到这一点。任何人都可以在这里提出一些想法,希望能解决这类事情的一般做法吗?

更新

我想我有个主意:

改变我的

template <template <typename...> class P, typename... Output>
struct remove_unit_packs<P<>, std::tuple<Output...>> {
    using type = P<Output...>;
};

从上到下

template <template <typename...> class P, typename... Output>
struct remove_unit_packs<P<>, std::tuple<Output...>> {
    using result = P<Output...>;
    using type = std::conditional_t<contains_unit_pack<result>::value,
        typename remove_unit_packs<result>::type,   
        result
    >;
};

我将定义template <typename Pack> struct contains_unit_pack。我的编译器目前抱怨循环逻辑或其他东西。如果有人认为这不会奏效,或者说可能有更好的方式,请告诉我。

3 个答案:

答案 0 :(得分:3)

不确定你究竟想要什么,但在我看来你必须做出递归is_unit_pack

这样的东西
template <typename T>
struct is_unit_pack : std::false_type
 { };

template <template <typename...> class C, typename ... Ts>
struct is_unit_pack<C<Ts...>>
   : std::integral_constant<bool,
        (2 > sumI<(false == is_unit_pack<Ts>{})...>::value)>
 { };

其中sumI是可变积分的总和

template <int ...>
struct sumI
 { static constexpr int value { 0 }; };

template <int I, int ... Is>
struct sumI<I, Is...>
 { static constexpr int value { I + sumI<Is...>::value }; };

以下是完整的编译示例

#include <tuple>
#include <type_traits>


template <typename...>
struct P
 { };

template <int ...>
struct sumI
 { static constexpr int value { 0 }; };

template <int I, int ... Is>
struct sumI<I, Is...>
 { static constexpr int value { I + sumI<Is...>::value }; };

template <typename T>
struct is_unit_pack : std::false_type
 { };

template <template <typename...> class C, typename ... Ts>
struct is_unit_pack<C<Ts...>>
   : std::integral_constant<bool,
        (2 > sumI<(false == is_unit_pack<Ts>{})...>::value)>
 { };

template <typename T, typename Output = std::tuple<>>
struct rupH
 { using type = T; };

template <template <typename...> class C, typename First, typename... Rest,
          typename... Output>
struct rupH<C<First, Rest...>, std::tuple<Output...>>
   : rupH<C<Rest...>, typename std::conditional<is_unit_pack<First>{},
        std::tuple<Output...>,
        std::tuple<Output..., typename rupH<First>::type>>::type>
 { };

template <template <typename...> class C, typename... Output>
struct rupH<C<>, std::tuple<Output...>>
 { using type = C<Output...>; };

template <typename T>
using rupH_t = typename rupH<T>::type;

template <typename T>
struct remove_unit_packs
 { using type = T; };

template <template <typename ...> class C, typename ... Ts>
struct remove_unit_packs<C<Ts...>>
 { using type = rupH_t<C<Ts...>>; };

template <typename T>
using remove_unit_packs_t = typename remove_unit_packs<T>::type;

int main ()
 {
   static_assert(std::is_same<
        remove_unit_packs_t<std::tuple<int, long>>,
        std::tuple<int, long>>{}, "!");

   static_assert(std::is_same<
        remove_unit_packs_t<std::tuple<int>>,
        std::tuple<int>>{}, "!");

   static_assert(std::is_same<
        remove_unit_packs_t<std::tuple<int, P<long>, long>>,
        std::tuple<int, long>>{}, "!");

   static_assert(std::is_same<
        remove_unit_packs_t<std::tuple<int, P<P<long>>, long>>,
        std::tuple<int, long>>{}, "!");

   static_assert(std::is_same<
      remove_unit_packs_t<std::tuple<int, P<float, P<bool>>, char>>,
      std::tuple<int, char>>{}, "!");

   static_assert(std::is_same<
      remove_unit_packs_t<std::tuple<int, P<float, P<char>, bool>, char>>,
      std::tuple<int, P<float, bool>, char>>{}, "!");
 }

如果你可以使用C ++ 17,sumI是不必要的,你可以使用fold表达式; is_unit_pack变得简单

template <typename T>
struct is_unit_pack : std::false_type
 { };

template <template <typename...> class C, typename ... Ts>
struct is_unit_pack<C<Ts...>>
   : std::integral_constant<bool,
        (2 > ((false == is_unit_pack<Ts>{}) + ...))>
 { };

答案 1 :(得分:1)

在C ++ 17中,很容易摆脱&#34;广度递归&#34;。结果,只有&#34;深度递归&#34;。基于max66's answeris_unit_type特征:

#include <array>
#include <tuple>
#include <type_traits>

template<class...> struct P {};

// same as in [max66's answer](https://stackoverflow.com/a/46985839/2615118)
template <typename T>
struct is_unit_pack : std::false_type
{ };

template <template <typename...> class C, typename ... Ts>
struct is_unit_pack<C<Ts...>>
  : std::integral_constant<bool,
               (2 > ((false == is_unit_pack<Ts>{}) + ...))>
{ };

template<template<class> class Pred, class... Ts>
constexpr size_t count_if() {
  return (size_t(0) + ... + Pred<Ts>{});
}

template<template<class> class Pred, class... Ts>
constexpr auto which() {
  constexpr auto flags = std::array{bool(Pred<Ts>{})...};
  constexpr size_t osize = count_if<Pred, Ts...>();
  auto inds = std::array<size_t, osize>{};
  auto it = inds.begin();
  for(size_t i=0; i<flags.size(); ++i) if(flags[i]) *it++ = i;
  return inds;
}

template<template<class> class Pred, class Ts, class Is>
struct keep_rec_if_impl;

template<template<class> class Pred, class T>
struct keep_rec_if {// non-specialized version for non-packs
  using R = T;// break the depth recursion (see below)
};

template<template<class> class Pred, class... Ts>
struct keep_rec_if<Pred, P<Ts...>> {// specialized version for packs
  static constexpr size_t osize = count_if<Pred, Ts...>();
  using IndSeq = std::make_index_sequence<osize>;
  using R = typename keep_rec_if_impl<Pred, P<Ts...>, IndSeq>::R;
};

template<template<class> class Pred, class... Ts>
using keep_rec_if_t = typename keep_rec_if<Pred, Ts...>::R;

template<template<class> class Pred, class... Ts, size_t... is>
struct keep_rec_if_impl<Pred, P<Ts...>, std::index_sequence<is...>> {
  static constexpr auto inds = which<Pred, Ts...>();
  template<size_t i> using T = std::tuple_element_t<inds[i], std::tuple<Ts...>>;
  using R = P<keep_rec_if_t<Pred, T<is>>...>;// depth-recursion only
};

template<template<class> class Pred, class Ts>
struct remove_rec_if {
  template<class U>
  using NotPred = std::integral_constant<bool, !Pred<U>{}>;
  using R = keep_rec_if_t<NotPred, Ts>;
};

template<template<class> class Pred, class Ts>
using remove_rec_if_t = typename remove_rec_if<Pred, Ts>::R;

template<class Ts>
using remove_unit_packs_t = remove_rec_if_t<is_unit_pack, Ts>;

int main () {
  static_assert(std::is_same<
        remove_unit_packs_t<P<int, long>>,
        P<int, long>>{}, "!");

  static_assert(std::is_same<
        remove_unit_packs_t<P<int>>,
        P<int>>{}, "!");

  static_assert(std::is_same<
        remove_unit_packs_t<P<int, P<long>, long>>,
        P<int, long>>{}, "!");

  static_assert(std::is_same<
        remove_unit_packs_t<P<int, P<P<long>>, long>>,
        P<int, long>>{}, "!");

  static_assert(std::is_same<
        remove_unit_packs_t<P<int, P<float, P<bool>>, char>>,
        P<int, char>>{}, "!");

  static_assert(std::is_same<
        remove_unit_packs_t<P<int, P<float, P<char>, bool>, char>>,
        P<int, P<float, bool>, char>>{}, "!");

  return 0;
}

答案 2 :(得分:0)

我的方法有效,但我必须接受max66的更原始的解决方案。

#include <type_traits>
#include <tuple>

namespace detail {
    template <bool...> struct any_of;

    template <>
    struct any_of<> : std::false_type {};

    template <bool... Bs>
    struct any_of<true, Bs...> : std::true_type {};

    template <bool First, bool... Rest>
    struct any_of<First, Rest...> : any_of<Rest...> {};
}

template <typename T>
struct is_unit_pack : std::false_type {};

template <template <typename> class P, typename T>
struct is_unit_pack<P<T>> : std::true_type {};

template <typename T> struct contains_unit_pack : std::false_type {};

template <template <typename...> class P, typename... Ts>
struct contains_unit_pack<P<Ts...>> : 
    detail::any_of<(is_unit_pack<Ts>::value || contains_unit_pack<Ts>::value)...> {};

// remove_unit_packs
template <typename T, typename Output = std::tuple<>>
struct remove_unit_packs {
    using type = T;
};

template <typename Result, bool>
struct check {
    using type = Result;    
};

template <typename Result>
struct check<Result, true> : remove_unit_packs<Result> {};  // The key recursive call.

template <template <typename...> class P, typename... Output>
struct remove_unit_packs<P<>, std::tuple<Output...>> :
    check<P<Output...>, contains_unit_pack<P<Output...>>::value> {};

template <template <typename...> class P, typename First, typename... Rest, typename... Output>
struct remove_unit_packs<P<First, Rest...>, std::tuple<Output...>> : std::conditional_t<is_unit_pack<First>::value,
    remove_unit_packs<P<Rest...>, std::tuple<Output...>>,
    remove_unit_packs<P<Rest...>, std::tuple<Output..., typename remove_unit_packs<First>::type>>  // We use 'typename remove_unit_packs<First>::type>' instead of simply 'First' in case 'First' contains a unit pack despite not being a unit pack (in which case all unit packs must be removed from within 'First').
> {};

template <typename T>
using remove_unit_packs_t = typename remove_unit_packs<T>::type;

// Test
template <typename...> struct P;

int main() {
   static_assert(std::is_same<
        remove_unit_packs_t<std::tuple<int, P<>>>,
        std::tuple<int, P<>>>{});

   static_assert(std::is_same<
        remove_unit_packs_t<std::tuple<int>>,
        std::tuple<int>>{});

   static_assert(std::is_same<
        remove_unit_packs_t<std::tuple<int, P<long>, long>>,
        std::tuple<int, long>>{});

   static_assert(std::is_same<
        remove_unit_packs_t<std::tuple<int, P<P<long>>, long>>,
        std::tuple<int, long>>{});

   static_assert(std::is_same<
      remove_unit_packs_t<std::tuple<int, P<float, P<bool>>, char>>,
      std::tuple<int, char>>{});

   static_assert(std::is_same<
      remove_unit_packs_t<std::tuple<int, P<float, P<char>, bool>, char>>,
      std::tuple<int, P<float, bool>, char>>{});

   static_assert(std::is_same<
      remove_unit_packs_t<std::tuple<int, P<float, P<char>, bool>, char>>,
      std::tuple<int, P<float, bool>, char>>{});

   static_assert(std::is_same<
      remove_unit_packs_t<std::tuple<int, P<P<float, P<bool>, P<long>>, int, P<void>>, P<P<char, bool>>>>,
      std::tuple<int>>{});
}