Does std::function
support perfect forwarding of arguments? I decided to test it:
struct Widget
{
void operator()(int const&)
{
std::cout << "lvalue";
}
void operator()(int&&)
{
std::cout << "rvalue";
}
};
std::function<void(int)> f{Widget()};
int x = 5;
f(x);
Guess which operator()
gets called - the one taking rvalue reference. It seems that it is by design. What is the rationale behind this behavior?
What is Perfect Forwarding. Perfect forwarding allows a template function that accepts a set of arguments to forward these arguments to another function whilst retaining the lvalue or rvalue nature of the original function arguments.
The std::forward function as the std::move function aims at implementing move semantics in C++. The function takes a forwarding reference. According to the T template parameter, std::forward identifies whether an lvalue or an rvalue reference has been passed to it and returns a corresponding kind of reference.
std::move takes an object and casts it as an rvalue reference, which indicates that resources can be "stolen" from this object. std::forward has a single use-case: to cast a templated function parameter of type forwarding reference ( T&& ) to the value category ( lvalue or rvalue ) the caller used to pass it.
When t is a forwarding reference (a function argument that is declared as an rvalue reference to a cv-unqualified function template parameter), this overload forwards the argument to another function with the value category it had when passed to the calling function.
Yes and no. Yes, the arguments are forwarded. But no, overload resolution is not done based on the arguments you provide at the point of the call - it's done based on the template arguments to std::function
.
std::function<void(int)>
means you have a callable that takes an int
, which implicitly is an int
rvalue. Calling Widget
with an rvalue of type int
prefers the int&&
overload over the int const&
overload, which is why that one is selected. It doesn't matter how you invoke the actual function
object, once you selected that you want void(int)
- that's the preferred overload.
The actual call operator behaves as if:
struct widget_function {
void operator()(int arg) const {
// ~~~~~ ~~~~
w_(std::forward<int>(arg));
// ~~~
}
Widget w_;
};
The underlined portions come from the signature you provide to std::function
. You can see that both f(x)
and f(5)
end up calling the same operator()
from Widget
.
In short, std::function<Sig>
type erases one single overload that satisfies Sig
. It does not type erase an entire overload set. You asked for void(int)
, so you get void(int)
and only void(int)
.
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