如何内省可变模板模板参数的arity

How to introspect the arity of a variadic template template argument?

本文关键字:arity 参数 何内省      更新时间:2023-10-16

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

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

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

即使是穷举的专门化也不是一种选择,因为采用另一个模板类型的模板类型在内部模板的参数数量方面可能不是部分专门化的。

这就引出了一个问题,我担心答案是否定的。

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

我不希望arity的实际实现采用模板类型的形式,例如在显而易见的方法中,也就是说,只要不依赖于实际的参数,在编译时可以计算的任何内容都可以被视为"合理"的解决方案。

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

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个元素时失败——相反,它可以找到每个应用程序的最长前缀,从而允许类似地捆绑尾部。

虽然采用模板模板参数的模板类型在其模板模板参数方面可能没有部分专门化,但函数可能会以这种方式过载。

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>();
};

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

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

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计算模板的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>));
}