Reputation: 5188
This example on the usage of std::forward
is puzzling me. This is my edited version:
#include <iostream>
#include <memory>
#include <utility>
using namespace std;
struct A{
A(int&& n) { cout << "rvalue overload, n=" << n << "\n"; }
A(int& n) { cout << "lvalue overload, n=" << n << "\n"; }
};
template<typename> void template_type_dumper();
template<class T, class U>
unique_ptr<T> make_unique(U&& u){
//Have a "fingerprint" of what function is being called
static int dummyvar;
cout<<"address of make_unique::dummyvar: "<<&dummyvar<<endl;
//g++ dumps two warnings here, which reveal what exact type is passed as template parameter
template_type_dumper<decltype(u)>;
template_type_dumper<U>;
return unique_ptr<T>(new T(forward<U>(u)));
}
int main()
{
unique_ptr<A> p1 = make_unique<A>(2); // rvalue
int i = 1;
unique_ptr<A> p2 = make_unique<A>(i); // lvalue
}
The output is
address of make_unique::dummyvar: 0x6021a4
rvalue overload, n=2
address of make_unique::dummyvar: 0x6021a8
lvalue overload, n=1
and the warnings about reference to template_type_dumper
show that in the first instantiation, decltype(u) = int&&
and U = int
, for the second decltype(u) = int&
and U = int&
.
It's evident that there are two different instantiations as expected, but her are my questions:
std::forward
work here? In the first instantiation, its template argument is explicitly U = int
, how can it know that it has to return a rvalue-reference? What would happen if I specified U&&
instead?make_unique
is declared to take a rvalue-reference. How come u
can be a lvalue-reference? Is there any special rule that I am missing?Upvotes: 1
Views: 455
Reputation: 234374
make_unique
is declared to take a rvalue-reference. How come u can be a lvalue-reference? Is there any special rule that I am missing?
make_unique
is declared to take a reference. What kind that reference is is to be deduced. If an lvalue of type foo
is passed, U
is deduced as foo&
and U&&
becomes foo&
because of the reference collapsing rules (basically, "combining" an lvalue reference with another reference always produces an lvalue reference; combining two rvalue references produces an rvalue reference). If an rvalue of type foo
is passed, U
is deduced as foo
and U&&
is foo&&
.
This is one of the things that powers perfect forwarding: with U&&
you can take both lvalues and rvalues, and U
is deduced to match the appropriate value category. Then with std::forward
you can forward the values preserving that same value category: in the first case, you get std::forward<foo&>
which forwards an lvalue, and in the second one, you get std::forward<foo>
which forwards an rvalue.
In the first instantiation, its template argument is explicitly U = int, how can it know that it has to return a rvalue-reference?
Because the return type of std::forward<T>
is always T&&
. If you pass int
it returns int&&
. If you pass int&
it returns int&
again because of the reference collapsing rules.
What would happen if I specified U&& instead?
You would have std::forward<int&&>
and the reference collapsing rules make int&& &&
an rvalue reference still: int&&
.
Upvotes: 4