Why passing rvalue reference (X&&) is AS IF passing lvalue reference (X&)?

610 views Asked by At

While I was studying rvalue reference, I found a strange answer from stackoverflow.

The questioner wants to avoid code duplication between a function that receive parameter as a lvalue reference, and another one is a function that receive a rvalue reference. Both functions do the same thing.

Here is the issue:-

void foo(X& x)  { /*complex thing*/       }      //#A
void foo(X&& x) { /*complex SAME thing*/  }      //#B

Here is the proposed solution. It is modified a bit by me:-

void foo(X& x)  {  /*complex thing*/ }      //#A
void foo(X&& x) { foo(x);            }      //#B

Question

Why doesn't my version lead to stack overflow exception?
In my version, why foo#B call foo#A, but not foo#B?

More specifically, which C++ rule enforces this behavior?

2

There are 2 answers

3
leslie.yao On BEST ANSWER

According to the rule of value categories, as a named parameter, x is an lvalue. Then foo#A will be invoked, because x could be bound to lvalue-reference, but not rvalue-reference.

Note that the fact that x is declared as an rvalue-reference has nothing to do with the value category of x.

lvalue

The following expressions are lvalue expressions:

  • the name of a variable or a function in scope, regardless of type, such as std::cin or std::endl. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression;

You can use std::move to make it an xvalue (rvalue), then foo#B will be selected (as you expected).

0
AudioBubble On

Although you have an answer already saying where the C++ standard specifies this, let me also answer why it specifies this.

The idea behind rvalue reference function parameters is that the functions may assume the referenced object, or at least its contents, will no longer be used after the function returns.

Now suppose you have

void f(const X&x); // doesn't modify x
void f(X&&x); // clears x's data

and you try

void g(X&&x) {
    f(x);
    std::cout << "we just called f(" << x << ")" << std::endl;
}

This wouldn't be useful if it called f(X&&), because x's contents would be gone before they could get printed.

g needs to explicitly tell f that it's okay to take over the object. f cannot assume that g won't need it again later.

It might work if the compiler could somehow figure out that x is no longer needed, when g's body no longer refers to x, but that's a very complicated problem to solve and unless the rules spell out exactly when it should and shouldn't be done, compilers won't implement it the same way, making it very likely that code that works on one compiler breaks on the next.