Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't std::is_invocable accept a non type template parameter

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;
}
like image 365
Curious Avatar asked Jan 31 '26 10:01

Curious


2 Answers

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.

like image 105
Barry Avatar answered Feb 03 '26 00:02

Barry


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);
like image 28
Guillaume Racicot Avatar answered Feb 03 '26 01:02

Guillaume Racicot