3

I recently stumbled upon std::is_invocable that is going to be introduced into the C++17 standard and I am wondering why it needs a user to provide a type for a function pointer as opposed to just providing the function pointer itself which might be more convenient, especially since non type template parameters can now be unconstrained.

What I mean can be explained in the example below

void hello_world() {
    cout << "Hello world" << endl;
}
int main() {
    cout << std::is_invocable_v<decltype(hello_world)> << endl;
    // as opposed to being able to do
    // cout << std::is_invocable_v<hello_world> << endl;

    return 0;
}
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
Curious
  • 20,870
  • 8
  • 61
  • 146
  • Because passing a function pointer as a template parameter will result in a chicken vs egg problem, see http://stackoverflow.com/questions/1174169/function-passed-as-template-argument (don't feel this one is actually a dupe). You'll have to define the type of the function as the first template parameter, but you need the remaining template parameters to do that. – Sam Varshavchik Mar 15 '17 at 14:46
  • One is a type, the other is a value. – Kerrek SB Mar 15 '17 at 15:00
  • 1
    @SamVarshavchik Dude, what are you talking about? C++17, `template auto`. – Barry Mar 15 '17 at 15:03

2 Answers2

8

I am wondering why it needs a user to provide a type for a function pointer as opposed to just providing the function pointer itself which might be more convenient

Because you always have the type of the callable that you want to test, but you don't always have the value of it as a constant expression. Sure, when you do have the value you have to write out decltype(foo) instead of just foo, but that seems like a fairly minor burden, and would cover a fairly percentage of the use-case. Not sure it'd be worth the added complexity of having a template <auto F, class... Args> is_invocable just so that, sometimes, you as the user don't have to write decltype.

Barry
  • 286,269
  • 29
  • 621
  • 977
4

The primary use for std::is_invocable is to use with types and template parameters. Not to be only usable by directly using function pointers.

Let's change your code a bit and add a useful case:

void callF(F function, Args&& args) {
    std::invoke(function, std::forward<Args>(args)...);
}

// Later, in your main

callF(hello_world);

You'd like to filter your function to not be callable when the invoke call would be invalid. You can use std::is_invokable just like that:

auto callF(F function, Args&& args) -> std::enable_if<std::is_invocable_v<F, Args...>> {
    std::invoke(function, std::forward<Args>(args)...);
}

As you can see, the types sent as arguments to std::is_invocable reflect the arguments sent to std::invoke.

As a bonus, much more than function pointers are supported. Function objects too, and even member function pointers are supported. Right now, you could use the callF function like that:

callF([](int i){ /* ... */ }, 8);

struct Test { void test() {} };

Test t;

callF(&Test::test, t);
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141