2

For the example

template <typename T>
void function(T&& arg)

Can somebody explain in detail how does it end that function signature becomes T& for lvalues and T&& for rvalues passed in ? I know that somehow(standard line needed) T -> T& in the case of lvalues and T -> T in case of revalues and then by combining & and && it results lvalue/rvalue reference.

Ghita
  • 4,465
  • 4
  • 42
  • 69
  • Not really a duplicate but http://stackoverflow.com/questions/3582001/advantages-of-using-forward has the answer. – pmr Oct 23 '12 at 14:27
  • 2
    `T -> T` in the case of rvalues, actually. And I highly recommend watching [this video by Scott Meyers on this specific topic](http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Scott-Meyers-Universal-References-in-Cpp11). – Xeo Oct 23 '12 at 14:32
  • 1
    I also recommend reading [this answer of mine](http://stackoverflow.com/a/8527373/500104) for a step-by-step explanation of how the deduction and reference collapsing works. – Xeo Oct 23 '12 at 14:46

2 Answers2

5

The rule is found in section 8.3.2p6.

If a typedef, a type template-parameter, or a decltype-specifier denotes a type TR that is a reference to a type T, an attempt to create the type "lvalue reference to cv TR" creates the type "lvalue reference to T", while an attempt to create the type "rvalue reference to cv TR" creates the type TR.

Or in tabular form:

TR   R

T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)
T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
Xeo
  • 129,499
  • 52
  • 291
  • 397
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • yes, but how do you explain T is T& and T is T&&. Is it always that for lvalues (be it of type reference or simple value) that we have T is T& ? – Ghita Oct 23 '12 at 14:47
  • I think what's missing here is how template argument deduction plays into this. – Xeo Oct 23 '12 at 14:51
  • @Ben in case I didn't make myself understood. In order to apply section 8.3.2p6 you have to say first T is T& or T is T -> composing to & and && – Ghita Oct 23 '12 at 14:52
  • 1
    @Ghita: the argument at the place of call is either an lvalue or an rvalue. That comes from the call, not from the template itself. Read the table as: if the object pass to the function is `TR` and the type of reference in the template is `R`, then the deduced type is ... – David Rodríguez - dribeas Oct 23 '12 at 15:00
  • 1
    @DavidRodríguez-dribeas but when a template type T is deduced from an rvalue reference the deduced type is not an rvalue reference: `template void foo(T or T&&); foo(1);` -> T is int. So reference collapsing doesn't actually apply there. – bames53 Oct 23 '12 at 15:49
  • @bames53: I am not sure I follow, the compiler knows that `1` is an *rvalue*, and if the template takes the argument by *rvalue-reference*, the table above will apply. The deduced type `T` (if you don't explicitly ask for `&&`) will never be an *rvalue-reference*, but if you take the argument as `T&&`, depending on the nature of the argument the `T&&` expression will end up representing `T&` or `T&&`. – David Rodríguez - dribeas Oct 23 '12 at 16:34
  • 1
    @DavidRodríguez-dribeas What I mean is that the type deduced from `1` is `int`, and therefore the type _template-parameter_ does not denote a reference type at all; this means that reference collapsing is not needed and the quoted paragraph does not apply. – bames53 Oct 23 '12 at 16:52
  • @bames53: Oh, ok, I see. Your complaint is that the quote is from 8.3.2p6 and not from 14.8.2.1p3. – David Rodríguez - dribeas Oct 23 '12 at 17:05
  • @DavidRodríguez-dribeas Actually I commented because I misread your statement; you said 'Read the table as:' which I saw as "Read the table _as-is_:', and of course the table _as-is_ doesn't cover the situation Ghita was asking about, though your alternative reading does. – bames53 Oct 23 '12 at 17:47
  • 1
    So basically this paragraph (14.8.2.1p3) "If P is a cv-qualified type, the top level cv-qualifiers of P’s type are ignored for type deduction. 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" says how things work if argument is an lvalue that one wants to bind to an rvalue ref – Ghita Oct 23 '12 at 19:00
4

This is thanks to the reference collapsing rules. Assume that U is a non-reference type; then:

    T = U             T & = U &       T && = U &&
If  T = U &  ,  then  T & = U &  and  T && = U &    .
    T = U &&          T & = U &       T && = U &&

Therefore, if your function argument can bind to an lvalue reference of type U, then T must be de­duced as U & in order for T && to become U &, and this is the only choice, since lvalues cannot bind to rvalue references. On the other hand, if your argument is an rvalue of type U, then T is de­duced as U so that T && becomes U && and your argument can bind.

The key point is that matching reference type is T && (and not T!). However, since arg itself is a named variable and thus an lvalue, you must use std::forward<T>(arg) to create an expression that's identical to the one with which your function was called.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084