Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 826
brunocodutra Avatar asked Apr 06 '15 16:04

brunocodutra


People also ask

Which of the following are valid reasons for using Variadic templates in C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.

What is the use of Variadic templates?

With the variadic templates feature, you can define class or function templates that have any number (including zero) of parameters. To achieve this goal, this feature introduces a kind of parameter called parameter pack to represent a list of zero or more parameters for templates.

What is a Variadic template C++?

A variadic template is a class or function template that supports an arbitrary number of arguments. This mechanism is especially useful to C++ library developers: You can apply it to both class templates and function templates, and thereby provide a wide range of type-safe and non-trivial functionality and flexibility.


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

like image 174
Yakk - Adam Nevraumont Avatar answered Oct 05 '22 00:10

Yakk - Adam Nevraumont