Reputation: 6636
Trying to understand why the following example fails to compile:
#include <functional>
template <typename F>
void f1(F&& f)
{
std::forward<F>(f)("hi");
}
template <typename F>
void f2(F&& f)
{
std::invoke(f, "hi"); // works but can't perfect forward functor
}
template <typename F>
void f3(F&& f)
{
std::invoke<F>(f, "hi");
}
int main()
{
f1([](const char*) {}); // ok
f2([](const char*) {}); // ok
f3([](const char*) {}); // error
}
cppreference says the following about std::invoke
:
- Invoke the Callable object
f
with the parameters args. As byINVOKE(std::forward<F>(f), std::forward<Args>(args)...)
. This overload participates in overload resolution only ifstd::is_invocable_v<F, Args...>
is true.
So why is f3
not equivalent to f1
?
Upvotes: 4
Views: 359
Reputation: 13040
std::invoke
is itself a function. In your case, its first parameter is a rvalue reference while f
is a lvalue, so the error occurs.
INVOKE(std::forward<F>(f), std::forward<Args>(args)...)
is executed after the function std::invoke
is properly selected and called. Basically, your lambda function is passed as follows:
original lambda in main -> the parameter of f3 -> the parameter of std::invoke -> the parameter of INVOKE
So the std::forward
in INVOKE(std::forward<F>(f), std::forward<Args>(args)...)
is used in the last step, while you need to forward the lambda in the middle step (the parameter of f3
-> the parameter of std::invoke
). I guess this is where your confusion comes.
Upvotes: 3
Reputation: 123
I guess you're getting this error:
note: template argument deduction/substitution failed:
note: cannot convert ‘f’ (type ‘main()::<lambda(const char*)>’) to type ‘main()::<lambda(const char*)>&&’
That's because inside the function f3
, f
is an L-value, but the invoke
expects an R-value. For template argument deduction/substitution to work, the types have to match EXACTLY.
When you perfect forward f
to invoke
, this issue is resolved as you passed an R-value originally from outside f3
.
Upvotes: 0
Reputation: 51916
Because you need to std::forward<F>(f)
to std::invoke()
:
template <typename F>
void f3(F&& f)
{
std::invoke<F>(std::forward<F>(f), "hi"); // works
}
Consider the difference between these two calls:
void f(const int&) { std::cout << "const int&" << std::endl; }
void f(int&&) { std::cout << "int&&" << std::endl; }
int main()
{
std::cout << "first" << std::endl;
int&& a = 3;
f(a);
std::cout << "second" << std::endl;
int&& b = 4;
f(std::forward<int>(b));
}
The output is
first
const int&
second
int&&
If you remove the const int&
overload, you even get a compiler error for the first call:
error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'int'
The std::forward()
is necessary for passing the correct type to std::invoke()
.
Upvotes: 2