如何反省可变参数模板模板参数的arity?

时间:2015-04-06 16:38:07

标签: c++ templates metaprogramming

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

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

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

甚至没有详尽的专门化是另一种选择,因为采用另一种模板类型的模板类型可能不会对内部模板的参数数量进行部分专门化。

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

  

有没有合理的方法来反省模板类型的实际效果?

我不希望arity的实际实现采用模板类型的形式,例如显而易见的方法,即可以计算的任何内容在编译期间,作为一个合理的&#34;解决方案,只要它不依赖于实际的论点。

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

3 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:2)

虽然模板模板参数的模板类型在其模板模板参数的arity方面可能不是部分专用的,但函数可能会以这种方式过载。

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

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

答案 2 :(得分:0)

这是我解决这个问题的方法。它计算模板的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>));

}