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.
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With