I have the code
void prints_one()
{ cout << "one" << endl; }
int main(int argc, char *argv[])
{
std::function<void()> foo;
foo = prints_one;
foo();
return 0;
}
It works as expected; it prints "one". What I don't know is which assignment operator prototype is being invoked in the assignment and how. Looking at cpp reference, it looks like it probably is this function
template <class Fn> function& operator= (Fn&& fn);
But if that is the prototype being called, I don't understand how a function can bind to a rvalue reference. Thanks!
Update: Thanks all, I'll read up on universal references. In regards to 40two's answer; this code prints that it is an rvalue reference:
template<class Fn>
class Foo {
public:
Foo() {}
Foo& operator=(Fn&& x)
{
std::cout << std::boolalpha << std::is_rvalue_reference<decltype(x)>::value << std::endl;
}
};
void prints_one()
{
cout << "one" << endl;
}
int main(int argc, char *argv[])
{
Foo<void()> bar;
bar = prints_one;
}
This prints true
std::function is a type erasure object. That means it erases the details of how some operations happen, and provides a uniform run time interface to them.
C++ Library - <functional> Instances of std::function can store, copy, and invoke any Callable target -- functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members.
It lets you store function pointers, lambdas, or classes with operator() . It will do conversion of compatible types (so std::function<double(double)> will take int(int) callable things) but that is secondary to its primary purpose.
Many std::function implementations will avoid allocations and use space inside the function class itself rather than allocating if the callback it wraps is "small enough" and has trivial copying.
The C++ standard is meant to be confusing! Otherwise, people like me couldn't play smart just because they were watching while the C++ standard got developed. To this end it was decided that the &&
notation applied to types shall mean two entirely different although also confusingly related things:
T
, the notation T&&
means that an rvalue reference is being declared.auto&& x = ...
or in function template template <typename T> void f(T&& x)
the notation is to mean: determine the type and "referenceness" of the argument. That the deduced type T
be of different kinds depending on what was passed as argument:
const
lvalue of type X
is being passed, the type T
will be deduced as X&
, and T&&
becomes X&
as a reference and an rvalue reference collapse into a reference.const
lvalue of type X
is being passed, the type T
will be deduced as X const&
, and T&&
becomes X const&
, due to the same reference collapsing.T
will be deduced as X
, and T&&
becomes X&&
as there is nothing to collapse.The motivations driving the rules on argument deduction is perfect forwarding: the argument should be forwarded to the next layer ideally look like the same kind of type as the argument which got deduced in the first places.
With this in mind, the assignment operator of std::function<...>
being called is indeed
template <typename F>
std::function<...>::function(F&& f)
where the argument of the function it just deduced accordingly. In your concrete example the function pointer being passed actually is an rvalue created on the fly by decaying the function type: functions aren't really objects and can't be passed along in C++. To access them other than calling a pointer to the function is being formed. Thus, the argument is an rvalue of type void(*)()
.
Quoting stuff from here:
T&&
isn't always an rvalue
reference.
&&
in a type declaration sometimes could mean an rvalue
reference, but sometimes it means either rvalue
reference or lvalue
reference.
I'll try to contribute to the above with an example. Consider the following piece of code:
#include <iostream>
#include <type_traits>
using namespace std;
template<class F>
void foo(F&& f)
{
std::cout << std::boolalpha << std::is_rvalue_reference<decltype(f)>::value << std::endl;
}
void prints_one()
{
cout << "one" << endl;
}
int main(int argc, char *argv[])
{
foo(prints_one);
foo([](){ cout << "lambda" << endl; });
return 0;
}
false
true
This mean that although foo
takes a T&&
as an input parameter, because of the fact that prints_one
is an lvalue
, input parameter f
will be initialized with an lvalue
, and consequently it will become an lvalue
reference.
On the other hand in the second call of foo
we pass a lambda
which is really a rvalue
reference. As such, the input parameter f
is initialized with an rvalue
and consequently it becomes an rvalue
reference.
Thus, whether input parameter is deduced to an lvalue
reference or an rvalue
reference depends on input parameter that is passed in foo
upon the time it is called.
Regarding the updated example:
You are defining a template class Foo
with an assignment operator Foo<T>::operator=(T&&)
.
In this case T&&
is not a universal reference because there's no type deduction.
It is rather an rvalue
reference, hence you are getting true
.
This is due to the fact that T
is already deducted by the class Foo<T>
. Consequently, there can't be type deduction for input parameters of member overloaded operator Foo<T>::operator=(T&&)
.
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