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&&>(¶m) ; //¶m since we passed &t in A
we get
int&& & and not `int& &&` please correct me if I am wrong
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
}