Home > front end >  Forwarding reference and argument deduction
Forwarding reference and argument deduction

Time:04-23

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 is int&& not int& but still calls fun(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.

  • Related