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;
}
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.
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With