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:
- 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");
}
- 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);
}