Counting arguments of an arbitrary callable with the C++ detection idiom

202 Views Asked by At

I've been using the C++ detection idiom create a metafunction for determining the number of arguments to an arbitrary callable. So far, I have this (full, compilable code at http://ideone.com/BcgDhv):

static constexpr auto max_num_args = 127;

struct any { template <typename T> operator T() { } };

template <typename F, typename... Args>
using callable_archetype = decltype( declval<F>()(declval<Args>()...) );
template <typename F, typename... Args>
using is_callable_with_args = is_detected<callable_archetype, F, Args...>;

template <typename F, size_t I = 0,  typename... Args>
struct count_args
  : conditional<is_callable_with_args<F, Args...>::value,
      integral_constant<size_t, I>,
      count_args<F, I+1, Args..., any>
    >::type::type
{ };

template <typename F, typename... Args>
struct count_args<F, max_num_args, Args...> : integral_constant<size_t, max_num_args> { };

This works great when none of the callable arguments are lvalue references:

void foo(int i, int j) { }
static_assert(count_args<decltype(foo)>::value == 2, "");

But when any of the arguments are lvalue references, this fails (for obvious reasons, since the callable archetype has a substitution failure):

void bar(char i, bool j, double& k);
static_assert(count_args<decltype(bar)>::value == 3, "doesn't work");

Does anyone know how to generalize this idea to make it work with lvalue references as well?

3

There are 3 best solutions below

0
On BEST ANSWER

Change this line:

struct any { template <typename T> operator T() { } };

to:

struct any {
  template <typename T> operator T&&() { }
  template <typename T> operator T&() { }
};

live example

We have both an lvalue and rvalue implicit casting operator. So, we ... good?

2
On

Building off of the answer from @Jarod42, a slightly better definition of any seems to do the trick in the vast majority of cases (excluding cases that cause callable_archetype to be a substitution error for other reasons; for instance, classes with deleted copy constructors, the invocation of which wouldn't be valid anyway):

struct any {
  template <typename T,
    typename = enable_if_t<
      not is_same<T, remove_reference_t<T>>::value
    >
  >
  operator T();

  template <typename T,
    typename = enable_if_t<
      is_same<T, remove_reference_t<T>>::value
    >
  >
  operator T&();

  template <typename T,
    typename = enable_if_t<
      is_same<T, remove_reference_t<T>>::value
    >
  >
  operator T&&();
};

This seems to work in all of the same cases as the previous answer without the exponential scaling.

Demo

0
On

The following works (for small max_num_args):

struct any    { template <typename T> operator T(); };
struct anyref { template <typename T> operator T&(); };

template <typename F, typename... Args>
using callable_archetype = decltype(std::declval<F>()(std::declval<Args>()...) );
template <typename F, typename... Args>
using is_callable_with_args = std::is_detected<callable_archetype, F, Args...>;

template <typename F, size_t I = 0,  typename... Args>
struct count_args
  : std::conditional<is_callable_with_args<F, Args...>::value,
      std::integral_constant<std::size_t, I>,
      std::integral_constant<std::size_t,
                             std::min(count_args<F, I+1, Args..., any>::value,
                                      count_args<F, I+1, Args..., anyref>::value)>
    >::type::type
{};

template <typename F, typename... Args>
struct count_args<F, max_num_args, Args...> :
    std::integral_constant<std::size_t, max_num_args> {};

Demo

But code has to be optimized, as the complexity is 2**max_num_args :/