【问题标题】:How to introspect the arity of a variadic template template argument?如何内省可变参数模板模板参数的arity?
【发布时间】:2015-04-06 16:38:07
【问题描述】:

考虑一个假设的元函数arity,它将任何元函数作为参数并返回其实际的数量。

以下明显的方法是不可能的,因为根据语言标准命名的内部模板模板参数仅在本地定义。

template<template<typename... args> class f>
struct arity
{
    static constexpr std::size_t value = sizeof...(args); //ERROR: undefined 'args'
};

甚至穷尽特化也不是替代方案,因为采用另一种模板类型的模板类型可能不会就内部模板的参数数量进行部分特化。

这让我想到了这个问题,我担心谁的答案是否定的。

是否有任何合理的方法来内省模板类型的实际数量?

我不希望arity 的实际实现采用模板类型的形式,例如在明显的方法中,也就是说,在编译时可能计算的任何东西都是可接受作为“合理”的解决方案,只要它不依赖于实际参数。

注意:为简单起见,假设只允许非可变元函数作为arity 的参数。

【问题讨论】:

  • 让您的主模板采用T,然后使用arity&lt;F&lt;Args...&gt;&gt; 对其进行专门化,其中Args... 是模板参数。然后你得到sizeof...(Args)的大小。
  • 不幸的是,这意味着你不能只传递类模板。
  • @0x499602D2 正如克里斯指出的那样,不幸的是,从原始问题的意义上说,这不是“合理的”,因为在需要模板类型的数量时,实际的参数可能不可用。
  • @brunocodutra 那么 AFAIK 几乎是不可能的。
  • 我怀疑这是可能的......如果f 本身是一个可变参数模板,你希望它返回什么?

标签: c++ templates metaprogramming


【解决方案1】:
template<class...> struct foo;
template<class X> struct foo<X>:std::true_type {};
template<class X, class Y, class Z> struct foo<X,Y,Z>:std::false_type {};

在任何朴素的模式匹配下,foo 具有无限的通透性。

在实践中,它具有13 的通风度。

一般来说,“这个模板的空气感是什么”这个问题是错误的问题。相反,“这些类型可以传递给这个模板吗”,或者“这些类型中有多少可以传递给这个模板”是更有用的。

寻找模板的通透性就像想要从可调用对象中提取签名一样。如果你知道如何称呼一个对象,问“我可以这样称呼它吗?怎么样?”是合理的;问“告诉我怎么称呼你”几乎总是被误导。

template<class...>struct types{using type=types;};
template<class types>struct types_length;
template<class...Ts>struct types_length<types<Ts...>>:
  std::integral_constant<size_t, sizeof...(Ts)>
{};

template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;

namespace details {
  template<template<class...>class Z, class types, class=void>
  struct can_apply : std::false_type {};

  template<template<class...>class Z, class...Ts>
  struct can_apply<Z,types<Ts...>,void_t<Z<Ts...>>>: std::true_type {};
};
template<template<class...>class Z, class...Ts>
struct can_apply : details::can_apply<Z,types<Ts...>> {};

以上回答了“我可以将某些类型应用于模板”的问题。

现在,您可以将一组类型的最长前缀应用于模板:

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

namespace details {
  template<class types, class=types<>>
  struct pop_back {};
  template<class T0, class...rhs>
  struct pop_back<types<T0>, types<rhs...>>:types<rhs...> {};
  template<class T0, class...Ts, class...rhs>
  struct pop_back<types<T0, Ts...>, types<rhs...>>:
    pop_back<types<T0,Ts...>,types<rhs...,T0>>
  {};
  template<class types>
  using pop_back_t = typename pop_back<types>::type;
}
template<class types>
using pop_back = details::pop_back_t<types>;

namespace details {
  template<template<class...>class Z, class types, class=void>
  struct longest_prefix {};
  template<template<class...>class Z, class...Ts>
  struct longest_prefix<
    Z,types<Ts...>,
    std::enable_if_t<can_apply<Z,Ts...>>
  >:
    types<Ts...>
  {};
  template<template<class...>class Z,class T0, class...Ts>
  struct longest_prefix<
    Z,types<T0, Ts...>,
    std::enable_if_t<!can_apply<Z, T0, Ts...>>
  >:
    longest_prefix<Z,pop_back_t<types<T0,Ts...>>>
  {};
}
template<template<class...>class Z, class...Ts>
using longest_prefix =
  typename details::longest_prefix<Z, types<Ts...>>::type;

namespace details {
  template<class types>
  struct pop_front;
  template<>
  struct pop_front<types<>> {};
  template<class T0, class...Ts>
  struct pop_front<types<T0,Ts...>>:types<Ts...>{};
  template<class types>
  using pop_front_t=typename pop_front<types>::type;
}

可以编写类似的代码,该代码采用一组类型和一个模板,并重复切掉可以传递给模板的一组类型的最长前缀。

(上面的代码肯定有错别字)。

template<class types>
using pop_front = details::pop_front_t<types>;
template<size_t n, template<class...>class Z, class T>
struct repeat : repeat< n-1, Z, Z<T> > {};
template<template<class...>class Z, class T>
struct repeat<0,Z,T> : tag<T> {};
template<size_t n, template<class...>class Z, class T>
using repeat_t = typename repeat<n,Z,T>::type;
template<template<class...>class Z, class types>
using longest_prefix_tail =
  repeat_t<
    types_length<longest_prefix<Z,Ts...>>{},
    pop_front,
    types<Ts...>
  >;

现在我们可以获取一个模板和一组类型,并通过依次将模板应用于这组类型的最长前缀来构建一组类型。

如果我们疯了,我们甚至可以进行回溯,这样如果我们的模板需要 2 或 3 个元素,而我们给它 4 个,它就不会尝试给它 3 个,然后在剩下 1 个元素时失败——相反,它可以找到每个应用程序的最长前缀,以允许类似地捆绑尾部。

【讨论】:

  • 这很有趣。我还没有想到这种方法来测试某个模板是否可以接受一定数量的参数。因此,您的can_apply 可以递归地用于测试某个模板是否可以处理越来越多的未指定参数,当然永远不会被实例化,直到它失败,然后它的数量将是 n - 1。当然它必须虽然专门针对可变参数情况,否则会使编译器递归到遗忘。如果您只是更新您的答案以明确这种方法,我很乐意接受。
【解决方案2】:

虽然采用模板模板参数的模板类型确实可能没有就其模板模板参数的数量进行部分特化,但函数可能以这种方式重载。

template<template<typename> class f>
constexpr std::size_t _arity(){return 1;}
template<template<typename, typename> class f>
constexpr std::size_t _arity(){return 2;}
template<template<typename, typename, typename> class f>
constexpr std::size_t _arity(){return 3;}
//...
template<template<typename...> class f>
constexpr std::size_t _arity(){return 0;}

template<template<typename... args> class f>
struct arity
{
    static constexpr std::size_t value = _arity<f>();
};

虽然不理想,但这种方法在合理的范围内有效,并且最接近我能想到的“合理”解决方案。但是,我仍在寻找一种不需要详尽枚举函数/类型的纯可变参数解决方案。

【讨论】:

    【解决方案3】:

    这是我解决这个问题的方法。它计算模板的数量 通过替换假类型。

    is_subs_success 检查是否可以将类型替换为可变参数模板:

    #include <boost/mpl/assert.hpp>
    
    #include <boost/mpl/bool.hpp>
    #include <boost/mpl/integral_c.hpp>    
    #include <boost/mpl/identity.hpp>
    #include <boost/mpl/void.hpp>
    
    #include <boost/mpl/eval_if.hpp>
    
    using namespace boost;
    
    /*
      is_subs_success<F, T...>::value == false
        ==> F<T...> causes a compile error  
    */
    template
    <
      template<typename... FuncArgs> class Func,
      typename... SubsArgs
    >
    class is_subs_success {
    
      typedef int success[1];
      typedef int failure[2];
    
      // if it's not possible to substitute overload
      template<typename...>
      static failure& test(...);
    
      // if it's possible to substitute overload
      template<typename... U>
      static success& test(typename mpl::identity<Func<U...> >::type*);
    
    public:
    
      typedef is_subs_success<Func, SubsArgs...> type;
    
      static bool const value =
        sizeof(test<SubsArgs...>(0)) == sizeof(success);
    
    };
    

    arity 计算模板的数量。它替换了模板中的假参数。如果替换导致编译错误,则继续添加一个参数:

    template
    <
      template<typename... FuncArgs> class Func
    >
    class arity {
    
      // Checks whether `U` is full set of `Func`'s arguments
      template<typename... U>
      struct is_enough_args;
    
      // Adds one more argument to `U` and continues iterations
      template<size_t n, typename... U>
      struct add_arg;
    
      template<size_t n, typename... SubsArgs>
      struct go : mpl::eval_if
          <
            is_enough_args<SubsArgs...>,
            mpl::integral_c<size_t, n>,
            add_arg<n, SubsArgs...>
          > {};
    
      template<typename... U>
      struct is_enough_args : is_subs_success<Func, U...> {};
    
      template<size_t n, typename... U>
      struct add_arg {
        typedef typename
          go<n + 1, mpl::void_, U...>::type type;
      };
    
    public:
    
      typedef typename go<0>::type type;
    
    };
    

    此解决方案仅适用于模板。 arity 永远不会返回 0

    简单检查:

    template<typename A>
    struct t1 {};
    
    template<typename A, typename B>
    struct t2 {};
    
    template<typename A, typename B, typename C>
    struct t3 {};
    
    int main() {
    
      BOOST_MPL_ASSERT((mpl::bool_<arity<t1>::type::value == 1>));
      BOOST_MPL_ASSERT((mpl::bool_<arity<t2>::type::value == 2>));
      BOOST_MPL_ASSERT((mpl::bool_<arity<t3>::type::value == 3>));
    
    }
    

    【讨论】:

    • 您依赖于为 Func<...> 定义的 ::type,但一般来说,甚至不需要定义 F<...>。
    • 差不多了,但是目标不是找到“足够”替换的 args 数量,而是 args 的总数。请注意,这些概念对于可变参数模板或具有默认参数的模板并不等效。抛开可变参数模板不谈,要走的路是递归地增加 args 的数量直到它失败,而不是直到它第一次工作。
    • @brunocodutra 看起来我误解了问题。也许我稍后会用它做点什么。谢谢:)
    猜你喜欢
    • 2016-12-01
    • 1970-01-01
    • 2011-12-22
    • 1970-01-01
    • 2013-09-14
    • 2014-09-08
    • 2014-04-12
    • 1970-01-01
    • 2012-03-28
    相关资源
    最近更新 更多