4

I am trying to familiarize myself with the concept of perfect forwarding. I have read this and this post here. I believe I am also comfortable with the reference collapsing concept. While reading this other post. I came up with the following question

Suppose we have the following example

Example-1
template <class T>
void foo(T &&t)
{
  bar(std::forward<T>(t));
}

and we pass it something like this

int i= 12;
foo(i);

Now I understand this i will be treated as a int& what I don't understand is why does the answered question in the above link mention that it will be treated as int& && which will collapse as int&. I am of the opinion that it will be treated as int&& & which will collapse to int& I understand the return type of both is the same but I would like to get the first part right. The reason why I think it is int&& & instead of int& && is mentioned below please correct me if I am wrong

When I pass in something like this

int i =12;
foo(i);

Then Example 1 becomes something like this

void foo(int &&t)
{
  bar(std::forward<int>(&t)); // t is a reference now ------>A
}

Now std::forward implementation is this

template<typename T>                // For lvalues (T is T&),
T&& std::forward(T&& param)         // take/return lvalue refs.
{                                   // For rvalues (T is T),
    return static_cast<T&&>(param); // take/return rvalue refs.
}

so when our solution is applied to it . It becomes

return static_cast<int&&>(&param) ; //&param since we passed &t in A

we get

int&& & and not `int& &&` please correct me if I am wrong
Community
  • 1
  • 1
James Franco
  • 4,516
  • 10
  • 38
  • 80
  • is `&t` in `std::forward` a typo ? – Piotr Skotnicki Aug 16 '15 at 08:00
  • `&t` forms a pointer, not a reference – Piotr Skotnicki Aug 16 '15 at 08:02
  • and your implementation of `std::forward` is wrong – Piotr Skotnicki Aug 16 '15 at 08:06
  • Do you mean the implementation that I posted or `return static_cast(&param) ` ? I got that from http://stackoverflow.com/questions/27501400/the-implementation-of-stdforward – James Franco Aug 16 '15 at 08:07
  • I mean there are two overloads for `std::forward`, one forcing an lvalue reference, and another forcing an rvalue reference – Piotr Skotnicki Aug 16 '15 at 08:08
  • 1
    In response to your (deleted) question - yes, `T` can be deduced as a reference type only when a parameter type is a forwarding reference, `T&&`. (Note: if `T` is a type template parameter, then `T&&` is **not an rvalue reference**, but a *forwarding-reference*) – Piotr Skotnicki Aug 16 '15 at 09:46
  • YEs I figured that part out thus I deleted the question. Thanks for replying though. – James Franco Aug 16 '15 at 09:58
  • Saying references are pointers behind the scenes is like sayimg when you draw a route on a map, behind the scenes you are flying a plane with a huge paint sprayer and putting a line on what the map represents. One way to implement references is with pointers, but saying references are pointers is confusing the territory for the map. – Yakk - Adam Nevraumont Aug 16 '15 at 10:46

1 Answers1

5

When you pass i as an argument to foo:

int i = 12;
foo(i);

expression i has an lvalue category, thus T is deduced as int&, so example 1 becomes:

void foo<int&>(int& && t) // `int& t` due to reference collapsing
//         |   \ /
//         +--> T  = int&
{//                   V
    bar(std::forward<int&>(t));
}

Now, in std::forward, the param's type is specified explicitly to be int&, so:

int& && std::forward<int&>(typename std::remove_reference<int&>::type& param)
//                    ^~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
//                       |
{//                      V          
    return static_cast<int& &&>(param); // `int&` due to reference collapsing
}

which restores the lvalue category of i.


To make it clear, this is what happens when the argument expression's value category is an rvalue:

foo(12); // -> foo<int>(12);
//                  |
//        v~~~~~~~~~+
void foo<int>(int && t) // `int&& t`
//        |   \ /
//        +--> T  =  int
{//                   V
    bar(std::forward<int>(t));
}//                   |
//                    V 
int && std::forward<int>(typename std::remove_reference<int>::type& param)
//                    ^~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
//                       |
{//                      V          
    return static_cast<int &&>(param); // `int&&`
}

And when the type template parameter is specified as int&& explicitly:

foo<int&&>(12);
//     ^~~~+
//         V
void foo<int&&>(int&& && t) // `int&& t` due to reference collapsing
//         |    \   /
//         |     \ /
//         +----> T = int&&
{//                    V
    bar(std::forward<int&&>(t));
}//                    |
//                     V 
int&& && std::forward<int&&>(typename std::remove_reference<int&&>::type& param)
//                     ^~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
//                        |
{//                       V          
    return static_cast<int&& &&>(param); // `int&&` due to reference collapsing
}
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160