18
votes

What are rvalues, lvalues, xvalues, glvalues, and prvalues? gives a good overview of the taxonomy of rvalues/lvalues, and one of the recent answers to that question (https://stackoverflow.com/a/9552880/368896) stresses the point that prvalues are "like" the old-style rvalues, whereas the new xvalues allow for "lvalue-like" behavior.

However, consider the following code:

class X {};
X foo() { return X(); }

int main()
{
    foo() = X(); // foo() is a prvalue that successfully appears on the lhs
}

In this example, the expression foo() is a prvalue that appears on the left-hand side, and accepts assignment.

That got me thinking - the logic that "xvalues" differ from "prvalues" because xvalues (glvalues that they are) can appear on the left-hand-side, seems to be broken by this example. Here we have a prvalue - which is not a glvalue - appearing successfully on the lhs and accepting assignment.

(Note: in the case of POD, the above example would not compile, so for POD, the distinction between xvalues and prvalues seems to make sense. Therefore, this question is specifically in regards to non-POD types.)

What, then, is the true difference in either allowed usage, or behavior, between an xvalue and a prvalue, that necessitates this distinction being written into the standard? A single example of a difference would be a fine alternative answer.

ADDENDUM

Pubby's comment was correct. The lifetime of a prvalue is extended by the compiler, but the lifetime of an xvalue is not.

So, here is an answer to the question:

Consider the following code:

// ***
// Answer to question, from Pubby's comment
// ***

class X
{
public:
    X() : x(5) {}
    int x;
};

X foo() { return X(); }
X&& goo() { return std::move(X()); } // terrible coding, but makes the point

int main()
{
    foo() = X();
    X&& x1 = foo(); // prvalue - lifetime extended!  Object resides directly on stack as return value
    X&& x2 = goo(); // xvalue - lifetime not extended.  Object (possibly polymorphic) resides somewhere else.
    x1.x = 6;
    x2.x = 7; // Danger!

    std::cout << x1.x << std::endl; // Just fine
    std::cout << x2.x << std::endl; // prints garbage in VS 2012
}

This demonstrates a difference in behavior between a prvalue, and an xvalue. Here we have identical client code except for the difference in binding (prvalue vs. xvalue).

As the sample code demonstrates, the lifetime of the prvalue is automatically extended, but the lifetime of the xvalue is not.

There are other obvious differences revealed, as well: For the prvalue, the object itself appears on the stack as the return value of the function; correspondingly, because a prvalue's static type is guaranteed to be its dynamic type (see answer below), extending its lifetime is meaningful and can be done by the compiler.

On the other hand, for the xvalue, the object is at some unknown, arbitrary location, so the compiler couldn't easily extend its lifetime, especially given that the type could be polymorphic.

Thanks for the answer.

2
AFAIK, X&& x = foo() would create a temporary if foo() is a rvalue, but wouldn't if foo() was an xvalue. Maybe that's a difference? (Although there's a good chance I'm wrong)Pubby
The left-hand-side of an assignment is not really a good rule. For example a const& is an lvalue and cannot appear in the lhs, and an rvalue of class type can appear at the lhs...David Rodríguez - dribeas
that class is a pod..Johannes Schaub - litb
@Pubby Excellent! Your comment is a correct answer. Please see my addendum. If you put it down as an answer, I will be able to award it.Dan Nissenbaum
A better example would be X&& x2 = std::move( X() );, which creates a prvalue that does still exist when x2 is initialized, but because the temporary is turned into an xvalue its lifetime is not extended it it dies at the end of the full expression.Jonathan Wakely

2 Answers

7
votes

For polymorphic nonpod type xvalue expressions, the dynamic type of the expression is generally unknown at compile time (so a typeid expression on them is evaluated, and virtual function calls cannot in general be devirtualized).

For prvalues, that does not apply. The dynamic type equals the static type.

Another difference is that decltype(e) is an rvalue reference type for xvalues and a non-reference type for prvalues.

Yet another difference is that an lvalue to rvalue conversion is not done for prvalues (they are already what the result would yield). This can be observed by some rather weird code

struct A { 
    int makeItANonPod; 
    A() = default;

  private:
    int andNonStdLayout;
    A(A const&) = default;
};

void f(...);

int main() {
  f(A()); // OK
  f((A&&)A()); // illformed
}
0
votes

What is the true difference between an xvalue and a prvalue? The xvalue is a kind of rvalue that can be cv-qualified and refer to an object and have dynamic type equal or unequal the static type.

const int&& foo();
int&& _v=foo();

Without xvalue, the return value of the above function foo can only be a rvalue. But build-in types have not const rvalue! Thus, the above non-const variable _v can always bind the return value of foo(), even we wish the foo() return a const rvalue.