How to explore the arity of a variation pattern template?

Consider the hypothetical metafunction arity , which takes any metafound as an argument and returns its real reality.

The following obvious approach is not possible, because by language standards the internal parameters of the template template are defined only locally.

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

Specializations that are not yet exhaustive are an alternative, since a template type that accepts another type of template cannot be partially specialized with respect to the number of arguments of the internal template.

This brings me to the question whose answer I'm afraid to be.

Is there any reasonable way to look at the real arity of a pattern type?

I do not expect the actual implementation of arity take the form of a template type, for example, in the obvious approach, that is, anything that can be computed at compile time is acceptable as a “reasonable” solution, as long as it does not depend on the actual arguments.

Note: for simplicity, it is assumed that only non-invariant arity allowed as arguments to arity .

+6
source share
3 answers
 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 endless airiness.

In practice, he has air either 1 or 3 .

In general, the question “what is the airflow of this template” is a wrong question. Rather, “whether these types can be passed to this pattern,” or “how many of these types can be passed to this pattern” is more useful.

Look for the availability of the template, as if you want to extract a signature from the called object. If you know how you are going to name the object, asking: "Can you call it that? How about this?" reasonably; asking "tell me how to call you" is almost always a mistake.

 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 the template".

Now, the longest type set prefix 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 set of types and a template, and can repeatedly cut out a long prefix of a set of types that can be passed to the template.

(The above code, of course, 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 set of types obtained by applying the template to the longest prefix of a bunch of types in turn.

If we were crazy, we could even roll back, so if our template accepts 2 or 3 elements and we feed it 4, he would not try to feed it 3, and then could not leave one element on the left - instead Of this, he could find the longest prefix of each application, which allows you to tie the tail in the same way.

+3
source

Although it is true that template types that accept template template parameters cannot be partially specialized with respect to the arity of its template template parameters, functions can be overloaded in this way.

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

Although not ideal, this approach works reasonably and is the closest to a “smart” solution that I could think of. However, I'm still looking for a purely variational solution that does not require an exhaustive listing of functions / types.

+2
source

This is my approach to this problem. It calculates the pattern regularity by substituting fake types.

is_subs_success checks if types can be replaced with a variation pattern:

 #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 not possible to substitute overload template<typename...> static failure& test(...); // if it 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 the arity of the template. It replaces fake arguments in the template. If the substitution causes a compilation error, it continues with another argument:

 template < template<typename... FuncArgs> class Func > class arity { // Checks whether `U` is full set of `Func` 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 just fine 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>)); } 
0
source

Source: https://habr.com/ru/post/984750/


All Articles