1

I'm new to C++ and I want to write code like std::invoke(std::stoi, "a") but I got some error:

No matching function for call to 'invoke'clang(ovl_no_viable_function_in_call)
functional(85, 5): Candidate template ignored: couldn't infer template argument '_Callable'

So I am confused. Could anyone tell me why I can't call std::stoi by std::invoke?

I think it is about default parameters or overloaded functions.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
hazy_wu
  • 11
  • 2
  • 2
    _Why_ do you want to call `std::stoi` via `std::invoke` instead of normally? – user17732522 Sep 24 '22 at 11:19
  • 2
    In any case, behavior is unspecified if you try to form a pointer or reference to a standard library function (except in a few specific exceptions, not including `std::stoi`) and you are trying to do that here by passing `std::stoi` to `std::invoke`. – user17732522 Sep 24 '22 at 11:20
  • 4
    ```stoi``` is overloaded for ```string``` and ```wstring```. Creating function pointers to overloaded functions is tedious. You probably don't need this. ```invoke``` has very specific uses. Why do you think you need it here? – Homer512 Sep 24 '22 at 11:21
  • @Homer512 @user17732522 Because My team try to use template function to catch exception and don't throw them, returning a error_code instead. so that I think I can use `std::invoke` to transfer all the function invoke. – hazy_wu Sep 24 '22 at 12:27
  • Well, I don't really get what you are doing but I understand that you did the right thing and built a minimal reproducible example and that's why the use of invoke seems unnecessary. To solve it, you can apply the techniques outlines here: https://stackoverflow.com/questions/2942426/how-do-i-specify-a-pointer-to-an-overloaded-function – Homer512 Sep 24 '22 at 12:30
  • @Homer512 thanks for your help! I think I should use other way to do this, just like macro definition. Have a nice night~ – hazy_wu Sep 24 '22 at 12:48
  • Personally, I would go with a lambda, like ```std::invoke([](const std::string& s) { return std::stoi(s); }, "a");``` – Homer512 Sep 24 '22 at 12:50

1 Answers1

0

The problem is that std::stoi has multiple overloads.

#include <functional>
#include <string>

int main() {
    std::invoke(std::stoi, "a");
}
$ g++ -o main main.cpp 
main.cpp: In function ‘int main()’:
main.cpp:5:16: error: no matching function for call to ‘invoke(<unresolved overloaded function type>, const char [2])’
    5 |     std::invoke(std::stoi, "a");
      |     ~~~~~~~~~~~^~~~~~~~~~~~~~~~
In file included from main.cpp:1:
/usr/include/c++/12/functional:107:5: note: candidate: ‘template<class _Callable, class ... _Args> std::invoke_result_t<_Callable, _Args ...> std::invoke(_Callable&&, _Args&& ...)’
  107 |     invoke(_Callable&& __fn, _Args&&... __args)
      |     ^~~~~~
/usr/include/c++/12/functional:107:5: note:   template argument deduction/substitution failed:
main.cpp:5:16: note:   couldn’t deduce template parameter ‘_Callable’
    5 |     std::invoke(std::stoi, "a");

There are two ways to solve this problem:

  1. Wrap the call in a generic lambda so that the decision about which overload to use is not made until the types of the arguments are known. This is the approach you should use!
#include <functional>
#include <string>

int main() {
    std::invoke([]<typename ... T>(T&& ... v) {
        return std::stoi(std::forward<T>(v) ...);
    }, "a");
}
  1. Alternatively, you can select the appropriate overload directly using static_cast and thus pass a unique reference to the function. Unfortunately, default arguments do not work with this approach and it is poorly readable as well.
#include <functional>
#include <string>

int main() {
    std::invoke(
        static_cast<int(&)(std::string const&, std::size_t*, int)>(std::stoi),
        "a", nullptr, 10);
}

Detailed explanation

Below you can see a simplified prototype of invoke. These four lines are at the beginning of each of the following programs.

template <typename Fn, typename ... T>
void invoke(Fn&& fn, T&& ... v){
    fn(static_cast<T&&>(v) ...);
}

Side note: static_cast<T&&> is the same as std::forward<T>.

We called with function as argument.

template <typename Fn, typename ... T>
void invoke(Fn&& fn, T&& ... v){ /* ... */ }

void function(int arg) {}

int main() {
    invoke(function, 5);
}

Since function has no overloads, everything works as expected.

As soon as you add an overload of function you get the same error as above.

void function(int arg) {}
void function(float arg) {}

int main() {
    invoke(function, 5);
}

A template has the same effect as a normal overload.

template <typename T>
void function(T arg1) {}

int main() {
    invoke(function, 5);
}
$ g++ -o main main.cpp 
main.cpp: In function ‘int main()’:
main.cpp:10:11: error: no matching function for call to ‘invoke(<unresolved overloaded function type>, int)’
   10 |     invoke(function, 5);
      |     ~~~~~~^~~~~~~~~~~~~
main.cpp:2:6: note: candidate: ‘template<class Fn, class ... T> void invoke(Fn&&, T&& ...)’
    2 | void invoke(Fn&& fn, T&& ... v){
      |      ^~~~~~
main.cpp:2:6: note:   template argument deduction/substitution failed:
main.cpp:10:11: note:   couldn’t deduce template parameter ‘Fn’
   10 |     invoke(function, 5);
      |     ~~~~~~^~~~~~~~~~~~~

If you have no overload but default argument, then the call itself works. But inside invoke an error occurs, because the passed reference of the function has no more information about the standard arguments, so that the call fails at this point.

void function(int arg1, float arg2 = 0.f) {}

int main() {
    invoke(function, 5);
}
$ g++ -o main main.cpp 
main.cpp: In instantiation of ‘void invoke(Fn&&, T&& ...) [with Fn = void (&)(int, float); T = {int}]’:
main.cpp:9:11:   required from here
main.cpp:3:7: error: too few arguments to function
    3 |     fn(static_cast<T&&>(v) ...);
      |     ~~^~~~~~~~~~~~~~~~~~~~~~~~~

You must be aware that it is not function that is passed, but a reference to a function that has an int and a float as parameters and that returns void. This is all the information the compiler has inside invoke.

A generic lambda contains a template, but is itself a unique structure.

void function(int arg1, float arg2 = 0.f) {}
void function(double arg) {}

int main() {
    invoke([]<typename ... T>(T&& ... v) {
        function(static_cast<T&&>(v) ...);
    }, 5);
}

is just another notation for

void function(int arg1, float arg2 = 0.f) {}
void function(double arg) {}

struct compiler_generated_lambda_name {
    template <typename ... T>
    void operator()(T&& ... v) const {
        function(static_cast<T&&>(v) ...);
    }
};

int main() {
    invoke(compiler_generated_lambda_name{}, 5);
}

compiler_generated_lambda_name{} creates an object of the class compiler_generated_lambda_name and this can of course normally be passed to invoke. Inside invoke the function operator operator() of the class is then called with the arguments passed to invoke. Thus, the instantiation of the operator() template was performed only when the types of the arguments were known.

In our case, of course, we know the type and argument at the time we call invoke, so a normal non-generic lambda can be used here.

void function(int arg1, float arg2 = 0.f) {}
void function(double arg) {}

int main() {
    invoke([](int v) { function(v); }, 5);
}

However, if we have this information, we do not need invoke for the call. In reality you use invoke only in generic code, so in the explanation I always used the generic variant as wrapper.

void function(int arg1, float arg2 = 0.f) {}
void function(double arg) {}

int main() {
    auto function_wrapper =
        []<typename ... T>(T&& ... v) {
            function(static_cast<T&&>(v) ...);
        };

    // the generic wrapper works always
    invoke(function_wrapper, 4);
    invoke(function_wrapper, 4, 2.f);
    invoke(function_wrapper, 4.2);
}
Benjamin Buch
  • 4,752
  • 7
  • 28
  • 51