As you might know, the std::function
type is a polymorphic wrapper around any function-like types. That include function pointer, classes with operator()
and lambdas.
Since it has to implement type erasure, it only checks if what you send is callable using the arguments it receives. The type erasure will simply do the conversion implicitly, just as any function calls.
If you look at the cppreference page for std::function::function
, you'll notice this requirement:
5) Initializes the target with std::move(f)
. If f
is a null pointer to
function or null pointer to member, *this
will be empty after the
call. This constructor does not participate in overload resolution
unless f
is Callable for argument types Args...
and return type R
.
Indeed, an expression of type X
can totally be bound to an rvalue reference of type X&&
. Try this:
X&& x = X{};
// likewise, both prvalue and xvalue works:
f4(X{});
f4(std::move(x));
In this example, X{}
is a prvalue of type X
. A rvalue-reference can be bound to such temporary.
Inside the std::function
, every parameter is forwarded. Forwarding is exactly as it sounds: it forward the parameter to call the wrapped function, just as if you called it directly. However forwarding don't keep the prvalue-ness, but forwards arguments as xvalue. xvalues and prvalues both are special cases of rvalue.
In the context of std::function
and forwarding, passing parameter to the wrapped function use the same expression for X
and X&&
. prvalue-ness cannot be forwarded without downsides.
To know more about forwarding, see What are the main purposes of using std::forward and which problems it solves?
Implicit conversion can go even further: from a double
to an int
. Indeed, if you send a floating type to a function that takes an int
, C++ will, sadly, perform an implicit conversion. The std::function
implementation is not immune to this effect. Consider this example:
#include <functional>
#include <cstdio>
int main() {
auto const f = std::function<void(double)>{
[](int a) {
std::printf("%d", a);
}
};
f(9.8); // prints 9
return 0;
}
Live on compiler explorer
This is possible only because std::function
is a wrapper around function objects. Function pointers would need to be the exact type.
Also, f2
doesn't compile because mutable reference to lvalue (X&
) cannot bound a temporary. So X& x = X{}
won't work, and X& x2 = std::move(x1)
won't work either.
Also, f4(x)
don't compile since x
is not a temporary. the expression (x)
is actually X&
. X&&
only bound to temporary, such as X{}
or std::move(x)
. To cast a variable to a temporary, you need to use std::move
, which preform the cast. See Why do I have to call move on an rvalue reference? for more details.