简体   繁体   中英

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

Consider a hypothetical metafunction arity , which takes any metafunction as argument and returns its actual arity.

The following obvious approach is not possible, since by language standards named inner template template parameters are only defined locally.

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

Not even exhaustive specializations are an alternative, since a template type taking another template type may not be partially specialized with respect to the number of arguments of the inner template.

This brings me to the question, whose answer I fear to be no.

Is there any reasonable way to introspect the actual arity of a template type?

I don't expect an actual implementation of arity to take the form of a template type, such as in the obvious approach , that is, anything which may be computed during compile time is acceptable as a "reasonable" solution, as long as it doesn't depend on the actual arguments.

Note: for simplicity assume only non-variadic metafunctions are allowed as arguments for 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 {};

under any naive pattern matching, foo has an infinite airity.

In practice, it has an airity of either 1 or 3 .

In general, the question "what is the airty of this template" is the wrong question. Rather, "can these types be passed to this template", or "how many of these types can be passed to this template" is a more useful one.

Looking for the airity of a template is like wanting to extract the signature from a callable object. If you know how you are going to call an object, asking "can I call it this way? How about that?" is reasonable; asking "tell me how to call you" is almost always misguided.

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...>> {};

the above answers the question "can I apply some types to a template".

Now, the longest prefix of a set of types you can apply to a template:

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

similar code that takes a bundle of types and a template, and repeatedly slices off the longest prefix of the bundle of types that can be passed to the template can be written.

(The above code certainly contains typos).

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...>
  >;

now we can take a template and a bunch of types, and build a bundle of types resulting from applying the template to the longest prefix of the bunch of types in turn.

If we where insane, we could even do backtracking, so that if our template takes 2 or 3 elements, and we feed it 4, it wouldn't try to feed it 3, then fail on having 1 element left -- instead, it could find the longest prefix of each application that allows the tail to be similarly bundled.

While it is true that template types taking template template parameters may not be partially specialized with respect to the arity of its template template parameters, functions may be overload in this manner.

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

While not ideal, this approach works within reasonable limits and is the closest to a "reasonable" solution that I could think of. However I'm still looking for a pure variadic solution which does not require exhaustive enumeration of functions/types.

It is my approach to this problem. It computes template's arity by substituting fake types.

is_subs_success checks whether it is possible to substitute types to variadic template:

#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 computes template's arity. It substitutes fake arguments in template. If substitution causes compile error, it continues with one more argument:

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;

};

This solution works fine only with templates. arity never returns 0 .

Simple check:

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

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM