19

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.

Community
  • 1
  • 1
Dan Nissenbaum
  • 13,558
  • 21
  • 105
  • 181
  • 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 Mar 18 '13 at 17:14
  • 3
    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 Mar 18 '13 at 17:20
  • @Yakk The code compiles for me in VS 2012 as-is. – Dan Nissenbaum Mar 18 '13 at 17:40
  • @Yakk: No, the implicitly declared `operator=` will be public. – Mike Seymour Mar 18 '13 at 17:40
  • 5
    that class *is* a pod.. – Johannes Schaub - litb Mar 18 '13 at 17:41
  • Oops -- the point remains. Write `X& operator=(X const& other)&=default` and I think you can block assignment to prvalues. – Yakk - Adam Nevraumont Mar 18 '13 at 17:42
  • @DavidRodríguez-dribeas - The assignment operator is being implicitly called in the line `foo() = X()`. – Dan Nissenbaum Mar 18 '13 at 17:42
  • @Yakk Is the & notation in the C++11 standard? Also - doesn't the single & limit the operation to *lvalues* (so that both xvalues AND prvalues would be excluded)? Sadly, I cannot test, because VS 2012 does not support the & syntax. – Dan Nissenbaum Mar 18 '13 at 17:51
  • 1
    @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 Mar 18 '13 at 18:14
  • The temporary's lifetime has already ended before you initialize `x2`, so there's nothing to extend. – Jonathan Wakely Mar 18 '13 at 18:26
  • 3
    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 Mar 18 '13 at 18:32
  • 1
    Please see also the standard 12.2.4 "(4) There are two contexts in which temporaries are destroyed at a different point than the end of the fullexpression. ... (5) The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except: ... The lifetime of a temporary bound to the returned value in a function return statement is not extended" – ThomasMcLeod Feb 03 '15 at 03:08

2 Answers2

7

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
}
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • This is an excellent answer. Note, however, that the answer indicates how the compiler sees things - it does not indicate an example of a difference in either usage or behavior (except, perhaps, a difference in efficiency in the case of prvalues). – Dan Nissenbaum Mar 18 '13 at 17:54
  • Also note that Pubby's comment (above) is an excellent example of a clear behavioral distinction between an xvalue and a prvalue; I've appended my answer to include sample code. – Dan Nissenbaum Mar 18 '13 at 18:15
  • 2
    @dan well i didnt put it as an example because it is an example that currently has a defect report open for it. Also, there is a defect report that will make `T().foo` an xvalue (if foo is an object member). binding a reference to it would cause lifetime extension of the surrouding T object aswell, all by the current rules. – Johannes Schaub - litb Mar 18 '13 at 20:31
  • 1
    @dan no. whether typeid is evaluated is a language difference. the expression may modify objects or has undefined behavior or throw exceptions when evaluated. – Johannes Schaub - litb Mar 18 '13 at 20:33
  • Is the defect report regarding the extension of the xvalue's lifetime, the lack of extension of the prvalue's lifetime, or something else? – Dan Nissenbaum Mar 18 '13 at 23:23
-1

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.

ligand
  • 182
  • 1
  • 5
  • 1
    The categories of prvalues and xvalues are disjoint, so an xvalue is not a kind of prvalue. Maybe you meant "The xvalue is a kind of rvalue..."? – Marc van Leeuwen Aug 15 '14 at 13:52