I'm reading about perfect forwarding, and this is something I've learnt that has confused me:
When you're trying to achieve perfect forwarding, you'll do something like this:
template<class T> // If I were to call foo with an l-value int, foo would look
void foo(T &&x); // like this: void foo(int& &&x)
So then I thought, wait, does that mean that if I did this:
template<class T> // If I were to call foo with an l-value int, foo would look
void foo(T x); // like this: void foo(int& x);
But that's not what happens. foo instead looks like this: void foo(int x);
My question: How come in the perfect forwarding function, T turns into a T& or T&&, but in the other one, T isn't a reference? Can somebody tell me the exact rules for this? I need some clarification!
A template parameter T
can be deduced as a reference type only if it appears in a function parameter of the form T&&
A function template of the form:
template<class T> void f(T x)
T
as an object type (and x
is an object type so is passed by value)template<class T> void f(T& x)
T
as an object type (and then x
has lvalue reference type)template<class T> void f(T&& x)
T
as
x
has lvalue reference type due to reference collapsing rules)x
has rvalue reference type)How come in the perfect forwarding function, T turns into a T& or T&&, [...]
This is wrong. T
becomes a reference type L&
or an object type R
, not a reference R&&
.
The function parameter of the form T&&
thus becomes
L&
(because adding an rvalue reference to an lvalue reference is still an lvalue reference, just like add_rvalue_reference<L&>::type
is still L&
)R&&
(because add_rvalue_reference<R>::type
is R&&
)This is because of the way type deduction is defined, and it is only related to perfect forwarding in the sense that the result of std::forward<>()
is an rvalue if an rvalue reference is passed, and an lvalue if an lvalue reference is passed.
But in general, when you do not have a reference to begin with, your T
is not going to be deduced as a reference type (i.e. as A&
, whatever A
could be). If that was the case, as Yakk correctly points out in the comments, it would be impossible to write a function template that takes its arguments by value.
In particular, the reference collapsing rule you are referring to is defined in Paragraph 14.8.2.1/4 of the C++11 Standard:
If P is a reference type, the type referred to by P is used for type deduction. If P is an rvalue reference to a cv-unqualified template parameter and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction. [ Example:
template <class T> int f(T&&); template <class T> int g(const T&&); int i; int n1 = f(i); // calls f<int&>(int&) int n2 = f(0); // calls f<int>(int&&) int n3 = g(i); // error: would call g<int>(const int&&), which // would bind an rvalue reference to an lvalue
—end example ]
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