1

In C++17 suppose I have a function-like object passed as a parameter to some template:

 template<typename F>
 void g(F f) {
    auto x = f(/*...*/);
 }

There are lots of different types F could be, such as a function pointer, a std::function, a lambda expression, and in fact any class type that implements operator().

Is there any way to get the function-like objects arity and type of its parameters and the type of its return type?

I mean, ultimately F could be a class that overloads operator() with multiple different member functions, each with different arities, parameter types and return types - so there isn't a fully-general answer (unless there is some way to iterate that overload set, which I don't think there is).

But for the typical case where a function call expression involving f results in a single overload, is there a solution?

(also if there is any progress in C++20, worth mentioning too)

Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • "*the typical case where a function call expression involving f results in a single overload*" Considering that a "single overload" could be a template function, I'm not sure how "typical" this case would be. – Nicol Bolas Feb 20 '21 at 05:21
  • I meant an `operator()` overload that's a template. Like what you get with generic lambdas and the like. – Nicol Bolas Feb 20 '21 at 05:33
  • @NicolBolas: Right, so if F has a member template `operator()` then that would instantiate to different things based on what arguments it is passed, so for the purposes of this question such a case is also out-of-scope (like multiple overloads). – Andrew Tomazos Feb 20 '21 at 05:47
  • My point is that it is a "typical case", so even if you can find an answer, it won't be a *useful* one. – Nicol Bolas Feb 20 '21 at 14:27
  • Seems similar to https://stackoverflow.com/questions/65498688/extract-signature-from-callable-type/ – Jeff Garrett Feb 20 '21 at 18:17
  • 1
    And IMO @NicolBolas is 100% correct. Outside of very limited situations, this is both the wrong question to ask and the answer will be too limited to have utility. – Jeff Garrett Feb 20 '21 at 18:24
  • @NicolBolas: @JeffGarrett: The motivating use case is calling the function-like object dynamically at run-time (think like Qt signals/slots, or exposing them to a scripting language like python, lua, javascript, etc). You can't instantiate a template at runtime, so an answer that covers a member template `operator()` is not possible. An answer that only covers cases with a specific signature (like an ordinary non-template function, std::function instantiation, non-generic lamdbda expression) is not only useful, it is in fact, exhaustively so. – Andrew Tomazos Feb 20 '21 at 23:55
  • @AndrewTomazos: You have failed to explain why calling a function requires knowing its interface like this. What are you trying to do by introspecting the functor? Isn't what matters is that it's callable given a set of parameters, *not* exactly how that happens? That is, if it is a template or overloaded member function, you can still call it so long as it takes the parameters you expect. So why do you need to introspect the callable like this? – Nicol Bolas Feb 21 '21 at 00:10
  • @NicolBolas: Take a look at a C++ binding system for your favorite scripting language and how it works when exporting a function. (say `boost::python` for a specific example: https://www.boost.org/doc/libs/1_75_0/libs/python/doc/html/tutorial/index.html) – Andrew Tomazos Feb 21 '21 at 00:29
  • @NicolBolas: Also, side note - the answer to your question is actually the same as the answer to why we can't have virtual member function templates. ;) – Andrew Tomazos Feb 21 '21 at 00:41

1 Answers1

5

C++17's adds deduction guides for std::function, which we can use to do the deduce the function signature of non-overloaded function-like objects:

template <typename R, typename... Args>
constexpr auto do_something_with_the_signature(std::function<R(Args...)>) {
    // Assuming that you only care about the return type.
    // Otherwise, you may want some kind of wrapper to extract the signature to
    // avoid runtime cost
}

...

using some_type_computation =
    decltype(do_something_with_the_signature(std::function(f)));

If you only wanted the return type, you could just use:

using result_type = typename decltype(std::function(f))::result_type;

If you want to avoid std::function altogether because of the compile-time costs, you can implement your own version of the deduction guides for your own type (possibly as general as a function_traits type trait). A sketch of how you might implement the deduction guides yourself can be seen in my answer here: https://stackoverflow.com/a/66038056/1896169

Justin
  • 24,288
  • 12
  • 92
  • 142