I'm trying to understand perfect forwarding a bit deeply and faced a question I can't figure out myself. Suppose this code:
void fun(int& i) {
std::cout << "int&" << std::endl;
}
void fun(int&& i) {
std::cout << "int&&" << std::endl;
}
template <typename T>
void wrapper(T&& i) {
fun(i);
}
int main()
{
wrapper(4);
}
It prints int&
. To fix this one should use std::forward. That's clear. What is unclear is why it is so.
What the code above unwraps into is:
void fun(int & i)
{
std::operator<<(std::cout, "int&").operator<<(std::endl);
}
void fun(int && i)
{
std::operator<<(std::cout, "int&&").operator<<(std::endl);
}
template <typename T>
void wrapper(T&& i) {
fun(i);
}
/* First instantiated from: insights.cpp:21 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void wrapper<int>(int && i)
{
fun(i);
}
#endif
int main()
{
wrapper(4);
return 0;
}
So i
should have rvalue type of int&&
. The question is: why do I need std::forward here since compiler already knows that i
is int&&
not int&
but still calls fun(it&)
?
CodePudding user response:
Types and value categories are different things.
Each C expression (an operator with its operands, a literal, a variable name, etc.) is characterized by two independent properties: a type and a value category.
i
, the name of the variable, is an lvalue expression, even i
's type is rvalue-reference.
The following expressions are lvalue expressions:
- the name of a variable, ... Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression;
- ...
That's why we should use std::forward
to preserve the original value category of a forwarding reference argument.
CodePudding user response:
why do I need
std::forward
here since compiler already knows that i isint&&
notint&
but still callsfun(it&)
?
The type of i
is int&&
, but i
itself is an lvalue. So when you're calling fun(i)
, since i
itself is an lvalue, the compiler will choose fun(int &)
.
If you want to invoke fun(int &&)
, you can use std::move
to cast it to an rvalue
fun(std::move(i));
CodePudding user response:
why do I need std::forward here since compiler already knows that i is int&& not int& but still calls fun(it&)?
Because i
when used as/in an expression such as the call fun(i)
is an lvalue. That is the value category of i
when used as/in an expression is lvalue
. Thus the call fun(i)
selects the first overload(void fun(int&)
).
On the other hand, the declared type of i
is int&&
i.e., an rvalue reference to int
.