使用简单的类型列表实现的指数编译时间。为什么?

Exponential compilation times with simple typelist implementation. Why?

我正在尝试 C++ 类型列表。下面是类型列表过滤器函数的简单实现。它似乎有效,只是 gcc 和 clang 的编译时间都超过了 18 个元素。我想知道我可以做些什么改进来使这个实用。

#include <type_traits>

// a type list
template <class... T> struct tl ;

// helper filter for type list 
template <class IN_TL, class OUT_TL, template <typename> class P>
struct filter_tl_impl;

// Base case
template <class... Ts, template <typename> class P>
// If the input list is empty we are done
struct filter_tl_impl<tl<>, tl<Ts...>, P> {
  using type = tl<Ts...>;
};

// Normal case
template <class Head, class... Tail, class... Ts2, template <typename> class P>
struct filter_tl_impl<tl<Head, Tail...>, tl<Ts2...>, P> {
  using type = typename std::conditional<
      // Does the predicate hold on the head of the input list?
      P<Head>::value,
      // The head of the input list matches our predictate, copy it
      typename filter_tl_impl<tl<Tail...>, tl<Ts2..., Head>, P>::type,
      // The head of the input list does not match our predicate, skip
      // it
      typename filter_tl_impl<tl<Tail...>, tl<Ts2...>, P>::type>::type;
};

template <class TL, template <typename> class P> struct filter_tl {
  using type = typename filter_tl_impl<TL, tl<>, P>::type;
};

// Test code
using MyTypes = tl<
   char*, bool, char, int, long, void,
   char*, bool, char, int, long, void,
   char*, bool, char, int, long, void
   >;


using MyNumericTypes = filter_tl<MyTypes, std::is_arithmetic>::type;

static_assert(std::is_same < MyNumericTypes,
              tl<
              bool,char,int,long,
              bool,char,int,long,
              bool,char,int,long
              >> :: value);

int main(int, char **) {}
using type = typename std::conditional<
      // Does the predicate hold on the head of the input list?
      P<Head>::value,
      // The head of the input list matches our predictate, copy it
      typename filter_tl_impl<tl<Tail...>, tl<Ts2..., Head>, P>::type,
      // The head of the input list does not match our predicate, skip
      // it
      typename filter_tl_impl<tl<Tail...>, tl<Ts2...>, P>::type>::type;

实例化双方因为::type.

您可能会在 std::conditional:

之后延迟中间实例化
using type = typename std::conditional<
      // Does the predicate hold on the head of the input list?
      P<Head>::value,
      // The head of the input list matches our predicate, copy it
      filter_tl_impl<tl<Tail...>, tl<Ts2..., Head>, P>,
      // The head of the input list does not match our predicate, skip
      // it
      filter_tl_impl<tl<Tail...>, tl<Ts2...>, P>>::type::type;

这导致实例化的数量是线性的,而不是指数的。

一个可能的替代方法是在 filter_tl_impl 中插入 std::conditional

我是说

// Normal case
template <typename Head, typename... Tail, typename... Ts2,
          template <typename> class P>
struct filter_tl_impl<tl<Head, Tail...>, tl<Ts2...>, P>
 {
   using type = typename filter_tl_impl<tl<Tail...>,
                                        std::conditional_t<
                                           P<Head>::value,
                                           tl<Ts2..., Head>,
                                           tl<Ts2...>>,
                                        P>::type;
};

而现在,为了一些完全不同的东西...

我建议将你的“正常情况”(递归情况)分成两种不同的情况:“真”情况和“假”情况。

不幸的是,这需要额外的自定义类型特征 check_first

template <typename, template <typename> class>
struct check_first : public std::false_type
 { };

template <typename H, typename ... T, template <typename> class P>
struct check_first<tl<H, T...>, P>
   : public std::integral_constant<bool, P<H>::value>
 { };   

现在可以这样写filter_tl_impl

// declaration and ground case
template <typename I, typename O, template <typename> class P,
          bool = check_first<I, P>::value>
struct filter_tl_impl
 { using type = O; };

// recursive-positive case
template <typename H, typename... T, typename... Ts,
          template <typename> class P>
struct filter_tl_impl<tl<H, T...>, tl<Ts...>, P, true>
   : public filter_tl_impl<tl<T...>, tl<Ts..., H>, P>
 { };

// recursive-negative case
template <typename H, typename... T, typename... Ts,
          template <typename> class P>
struct filter_tl_impl<tl<H, T...>, tl<Ts...>, P, false>
   : public filter_tl_impl<tl<T...>, tl<Ts...>, P>
 { };

我也将 filter_tl 重写为更简单的 using

template <typename TL, template <typename> class P>
using filter_tl = typename filter_tl_impl<TL, tl<>, P>::type;

所以你的原始代码变成了

#include <type_traits>

// a type list
template <typename...>
struct tl;

template <typename, template <typename> class>
struct check_first : public std::false_type
 { };

template <typename H, typename ... T, template <typename> class P>
struct check_first<tl<H, T...>, P>
   : public std::integral_constant<bool, P<H>::value>
 { };   

// declaration and ground case
template <typename I, typename O, template <typename> class P,
          bool = check_first<I, P>::value>
struct filter_tl_impl
 { using type = O; };

// recursive-positive case
template <typename H, typename... T, typename... Ts,
          template <typename> class P>
struct filter_tl_impl<tl<H, T...>, tl<Ts...>, P, true>
   : public filter_tl_impl<tl<T...>, tl<Ts..., H>, P>
 { };

// recursive-negative case
template <typename H, typename... T, typename... Ts,
          template <typename> class P>
struct filter_tl_impl<tl<H, T...>, tl<Ts...>, P, false>
   : public filter_tl_impl<tl<T...>, tl<Ts...>, P>
 { };

template <typename TL, template <typename> class P>
using filter_tl = typename filter_tl_impl<TL, tl<>, P>::type;

// Test code
using MyTypes = tl<char*, bool, char, int, long, void,
                   char*, bool, char, int, long, void,
                   char*, bool, char, int, long, void>;

using MyNumericTypes = filter_tl<MyTypes, std::is_arithmetic>;

static_assert(std::is_same_v<MyNumericTypes,
                             tl<bool, char, int, long,
                                bool, char, int, long,
                                bool, char, int, long>>);

int main ()
 { }

如果你想要列表,你要做的第一件事就是定义 cons 函数。剩下的就自然而然了。

// first, define `cons`      
  template <class Head, class T> struct cons_impl;
  template <class Head, class ... Tail>
  struct cons_impl <Head, tl<Tail...>> {
     using type = tl<Head, Tail...>;
  };
  template <class Head, class T>
  using cons = typename cons_impl<Head, T>::type;

// next, define `filter`
  template <template <typename> class P, class T>
  struct filter_tl_impl;
  template <template <typename> class P, class T>
  using filter_tl = typename filter_tl_impl<P, T>::type;

// empty list case      
  template <template <typename> class P>
  struct filter_tl_impl<P, tl<>> {
    using type = tl<>;
  };
  
// non-empty lust case
  template <template <typename> class P, class Head, class ... Tail>
  struct filter_tl_impl<P, tl<Head, Tail...>> {
    using tailRes = filter_tl<P, tl<Tail...>>;
    using type = std::conditional_t<P<Head>::value,
                                    cons<Head, tailRes>,
                                    tailRes>;
  };

注意tailRes只是为了可读性而定义的,你可以直接写

    using type = std::conditional_t<P<Head>::value,
                                    cons<Head, filter_tl<P, tl<Tail...>>>,
                                    filter_tl<P, tl<Tail...>>>;

并且编译时间可以忽略不计。