Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When you perfect-forward, typename T becomes a T& or T&&, but when you don't, T isn't a reference at all. How?

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!

like image 946
Aaron Avatar asked Apr 24 '13 13:04

Aaron


2 Answers

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)
    will deduce T as an object type (and x is an object type so is passed by value)
  • template<class T> void f(T& x)
    will deduce T as an object type (and then x has lvalue reference type)
  • template<class T> void f(T&& x)
    will deduce T as
    • either an lvalue reference (so x has lvalue reference type due to reference collapsing rules)
    • or as an object type (so 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

  • either 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&)
  • or it becomes R&& (because add_rvalue_reference<R>::type is R&&)
like image 164
Jonathan Wakely Avatar answered Oct 20 '22 18:10

Jonathan Wakely


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 ]

like image 42
Andy Prowl Avatar answered Oct 20 '22 19:10

Andy Prowl