0

I'd like to be able to infer the first argument of a callable. I can make it work for free and member functions, but I'm struggling with lambdas. Is there some trick I can use?

Here's an example. Within the match functions below, I want to use the knowledge of T.

template<class T>
void match(void (*)(T*, int))   { /* First */ }

template<class T>
void match(void (T::*)(int))    { /* Second */ }

template<class T>
void match(std::function<void(T,int)>)    { /* Third */ }

struct A
{
   void f(int)  {}
};

void g(A*, int) {}

match(&A::f);           // Ok, matches first
match(&g);              // Ok, matches second
match([](A*, int) {});  // Not Ok
match([&](A*, int) {}); // Not Ok
foxcub
  • 2,517
  • 2
  • 27
  • 27
  • 1
    Related to [is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda](http://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda) – Jarod42 Oct 20 '16 at 19:03
  • @Jarod42 Brilliant! That's exactly what I was looking for! – foxcub Oct 20 '16 at 19:15

2 Answers2

2

You cannot.

template<class T>
void g(T*, int) {}

fails to work

void g(void*, int) {}
void g(std::string**, int) {}

fails to work.

The same problem holds with lambdas.

As a general rule, you can ask "can I invoke X with type Y", you cannot get the signature.

std::function is not a lambda, and a lambda is not a std::function. They are unrelated types, other than the fact you can convert a lambda to a std::function with any compatible signature, just like you can convert any callable object.

If you restrict your problem space enough, you could write a traits class to extract the signature of operator() on the incoming object, and treat that as the arguments to a lambda.

This is a bad idea in C++11, and it generally gets worse in C++14 and C++17. [](auto a, int b){} is a lambda in C++14 (and many C++11 compilers support it), and it has no fixed type for the first argument.

Usually a better approach is to bundle the signature up separately than the callable. This violates DRY (Don't Repeat Yourself) in C++11, but in C++14 the lambda can just take auto&& parameters.

Another approach is to ask the question "which of these types work", which can be done. Usually you don't have an unlimited family of types you are working with, but rather an enumerated set.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • That's a little disappointing. Why is it that there is a way to get at the return type, but not the argument types of arbitrary callables? – foxcub Oct 20 '16 at 18:22
  • @foxcub but is there? `struct foo{ int operator()(int); double operator()(double); };` what is the deduced first argument and the return type? Speaking of, what's the first auugment of a generic lambda? – krzaq Oct 20 '16 at 18:26
  • @krzaq I see your point with the overloaded `operator()(...)`. I'm confused about your last question. Assuming a lambda has more than one argument, its first argument is unambiguous, isn't it? – foxcub Oct 20 '16 at 18:49
  • @Yakk Can you elaborate on the following? "If you restrict your problem space enough, you could write a traits class to extract the signature of operator() on the incoming object, and treat that as the arguments to a lambda." – foxcub Oct 20 '16 at 18:51
  • 1
    @foxcub not when it's a template. `[](auto... a){ return some_variadic_func(a...); }` – krzaq Oct 20 '16 at 18:54
  • @krzaq Hm, I guess I was thinking of C++11, but when you have this kind of `auto...` construct, is there no way to extract the types? Through `decltype` or something? I mean, if this was `template f(Args...)`, it wouldn't be too difficult to figure out what the first type is. – foxcub Oct 20 '16 at 18:59
  • 1
    Not without an actual invocation, since a template can accept many different types. In C++11 you could still have templated operator() in a class, with the same problems. – krzaq Oct 20 '16 at 19:01
0

I know only a way: pass through a std::function

match(std::function<void(A*, int)>([](A*, int) {}));
match(static_cast<std::function<void(A*, int)>>([&](A*, int) {}));
max66
  • 65,235
  • 10
  • 71
  • 111
  • Sure, that I have, but that's way too verbose. And it doesn't actually infer anything, it requires the user to specify what the argument type is by providing it to `std::function`. – foxcub Oct 20 '16 at 18:20
  • @foxcub - I know; if you show us how do you inted to use the function in `match()` (important: do you want to pass to `match()` the parameters to call the function?), maybe someone can detect a better way – max66 Oct 20 '16 at 18:39