Is the rvalue passed to the function still rvalue or not?

112 views Asked by At

I'm trying to understand everything I can about transfer semantics, rvalue, xvalue, prvalue, etc. I have the following function:

void f(int&& x, int i)
{
    std::cout << boost::typeindex::type_id_with_cvr<decltype(x)>().pretty_name();
}

Then I get the output: int&&

Good. So, x, even inside function, is an rvalue reference?

The boost::typeindex::type_id_with_cvr function suggests that, even within the function, input rvalue reference parameter is still an rvalue reference. However, from what I read on the internet, the rvalue passed to the function, inside the function, becomes lvalue reference.

the rvalue reference from the parameter is only used to select the function from the caller's perspective, it behaves like an lvalue reference inside the function

is rvalue passed as parameter treated as lvalue inside the function?

And so, when inside function f, I've tried to call function f again; suddenly it turned out that x is no longer an rvalue at all:

void f(int&& x, int i)
{
     std::cout << boost::typeindex::type_id_with_cvr<decltype(x)>().pretty_name();
     i++;
     if (i < 10)
     {
         f(x, i);
     }
}
int main(int, char**)
{
     int i = 0;
     f(50,i);
}

Result: "cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’".

So what is the type of x inside a function after all, and who is wrong here?

I thought this:

  1. 50 is an rvalue and evaluating the expression 50 returns an rvalue reference
  2. rvalue reference int&& x is an lvalue and therefore returns lvalue reference (?)

Is this the correct way of reasoning?

Or is the boost library wrong? Or maybe is compiler returning a bad error? Why in error massage is lvalue of type int and not int&?

If the parameter behaves as an lvalue reference inside the function (as it says in the stack thread I linked), why doesn't the error say int&?

And I don't mean just to get the answer what is the type of x inside the function. I'm more interested in understanding why the boost library says one thing, the error says something else, and the theoretical knowledge of C++ says something else.

1

There are 1 answers

0
Jan Schultke On

You are mixing two separate concepts, and are getting royally confused.

  • declarations can be references (such as lvalue ref., rvalue ref.)
    • for example, the parameter x inside f is an rvalue reference, because it is declared as such
  • expressions have value categories (lvalue, xvalue, prvalue)
    • for example, the expression x when it appears inside f is an lvalue (unless it is located in a return statement)

x is an rvalue reference when talking about the function parameter, but x is an lvalue when talking about the expression as it appears in other code. When you understand the difference, all your questions are easy to answer:

Good. So, x, even inside function, is an rvalue reference?

Yes, the parameter x of the function f is an rvalue reference. That's exactly what decltype(x) gives you: the declared type, as the name suggests.

However, from what I read on the internet, the rvalue passed to the function, inside the function, becomes lvalue reference.

This is inaccurate. The function parameters are lvalues when they appear in an some expression, but what type they have depends entirely on how you've declared the function. For example:

void foo(int x) {
    x;            // lvalue
    std::move(x); // xvalue
    int{x};       // prvalue
    // note: these three categorizations are exactly the same,
    //       no matter whether the type of x is an object, lvalue ref., or rvalue ref.
}

int y = ...;
foo(y);            // pass an lvalue of type int to foo()
foo(std::move(y)); // pass an xvalue of type int to foo()
foo(50);           // pass a prvalue of type int to foo()

So what is the type of x inside a function after all, and who is wrong here?

The type of the expression x is int, and the value category of the expression x is lvalue. The type of the function parameter x is int&&.

50 is an rvalue and evaluating the expression 50 returns an rvalue reference

50 is a prvalue, but evaluating it doesn't produce a reference. It produces a temporary object of type int.

rvalue reference int&& x is an lvalue and therefore returns lvalue reference (?)

That sentence doesn't make sense. A type isn't inherently an lvalue or rvalue. The value category depends on the expression and the context in which x is used.

If the parameter behaves as an lvalue reference inside the function (as it says in the stack thread I linked), why doesn't the error say int&?

Because the type of the parameter x is int&&. If you use decltype, that's exactly what it tells you.

And I don't mean just to get the answer what is the type of x inside the function. I'm more interested in understanding why the boost library says one thing, the error says something else, and the theoretical knowledge of C++ says something else.

The type of the parameter x is int&&, and the type of the expression x is int. The boost utility you're using works based on the type of the parameter, not based n the type of an expression x.

If you wanted to, you could obtain the type of the expression x be with:

std::remove_reference_t<decltype(x)>

This is obviously different from decltype(x), which yields the type of the declaration.