Search code examples
c++templatesperfect-forwarding

Forwarding reference and argument deduction


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&)?


Solution

  • 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.