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&)
?
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 the variable'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.