4

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?

Daisy Sophia Hollman
  • 6,046
  • 6
  • 24
  • 35
  • This is pretty brittle. (And it doesn't handle arbitrary Callables, either). – T.C. Apr 12 '16 at 20:24
  • 1
    @T.C. What arbitrary callables did you have in mind that it doesn't handle? – Daisy Sophia Hollman Apr 12 '16 at 21:47
  • Pointers to members. – T.C. Apr 13 '16 at 08:20
  • @T.C. True, but that generalization is not hard to make and just complicates the question. All that is needed is additional archetypes for all forms of INVOKE() in the specification of Callable and a big or statement. – Daisy Sophia Hollman Apr 15 '16 at 19:38
  • Not quite. You can't do the full INVOKE logic with expression SFINAE alone (or at least, not easily). (A cheat is to use SFINAE-friendly `result_of`.) – T.C. Apr 18 '16 at 10:31
  • @T.C. Isn't that essentially what the standard library does in the `functional` header? (e.g., circa line 269 of https://github.com/gcc-mirror/gcc/blob/3cb1e7bf3c9673970832d3dec625a08512c6f530/libstdc%2B%2B-v3/include/std/functional) – Daisy Sophia Hollman Apr 27 '16 at 17:00
  • 1
    It uses a tag to constrain it, instead of expression SFINAE. Look at the hoops its `result_of` jumps through to get the right tag. – T.C. Apr 27 '16 at 17:41

3 Answers3

3

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 :/

Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

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?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

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

Daisy Sophia Hollman
  • 6,046
  • 6
  • 24
  • 35