What Expressions Create Xvalues

What expressions create xvalues?

There is a helpful non-normative note in the introduction to §5 (C++11 §5[expr]/6):

[ Note: An expression is an xvalue if it is:

  • the result of calling a function, whether implicitly or explicitly, whose return type is an rvalue reference to object type,

  • a cast to an rvalue reference to object type,

  • a class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue, or

  • a .* pointer-to-member expression in which the first operand is an xvalue and the second operand is a pointer to data member.


In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether named or not. —end note ]

Searching through the rest of §5, this list appears exhaustive. The list is followed by an example:

struct A {
int m;
};

A&& operator+(A, A);
A&& f();
A a;
A&& ar = static_cast<A&&>(a);

The expressions f(), f().m, static_cast<A&&>(a), and a + a are xvalues. The expression ar is an lvalue.

There are two common ways to get an xvalue expression:

  • Use std::move to move an object. std::move performs a static_cast to an rvalue reference type and returns the rvalue reference.

  • Use std::forward to forward an rvalue. std::forward is typically used in a function template to enable perfect forwarding of a function argument.

    If the argument provided to the function template was an rvalue, the parameter type will be an rvalue reference, which is an lvalue. In this case, std::forward performs a static_cast to an rvalue reference type and returns the rvalue reference.

    (Note: If the argument provided to the function template was an lvalue, the parameter type will be an lvalue reference and std::forward will return an lvalue reference.)

How expressions designating temporary objects are xvalue expression?

situation 3, 4 and 7.

7 (discarded expression) is the easiest:

42; // materialize and discard
std::string{"abc"}; // materialize and discard

3 (doing things to array rvalue) requires knowing how to make them

using arr_t = int[2][3];
int a = arr_t{}[0][0]; // have to materialize to be able to subscript

4 (making an std::initializer_list) is what it says on the tin

std::initializer_list<std::string>{
"abc"s,
"def"s
}; // have to materialize the two strings
// to place them in the secret array pointed to by the list

What are xvalues in C++

Why didn't they say An xvalue is an glvalue or an rvalue?

Because rvalue had not been defined yet and because rvalue would be defined in terms of xvalue. One has to be defined without relying on the other to avoid the definition from becoming circular.

Also because xvalue is both a glvalue and an rvalue.

Furthermore, xvalue is a glvalue that is not an lvalue. Even further, xvalue is an rvalue that is not a prvalue. Lastly, xvalue is an expression that is neither lvalue nor prvalue.

I don't understand why they only mentioned gvalues in that sentence

Only glvalue and prvalue had been defined. See earlier mention of avoiding circular definition.

What are xvalues in C++

Well, you've seen its definition. The standard has following non-normative note that gives further information:

[basic.lval]

[ Note: An expression is an xvalue if it is:

  • the result of calling a function, whether implicitly or explicitly, whose return type is an rvalue reference to object type ([expr.call]),
  • a cast to an rvalue reference to object type ([expr.type.conv], [expr.dynamic.cast], [expr.static.cast] [expr.reinterpret.cast],
    [expr.const.cast], [expr.cast]),
  • a subscripting operation with an xvalue array operand ([expr.sub]),
  • a class member access expression designating a non-static data member of non-reference type in which the object expression is an
    xvalue ([expr.ref]), or
  • a .* pointer-to-member expression in which the first operand is an xvalue and the second operand is a pointer to data member
    ([expr.mptr.oper]).

Are temporary objects xvalues?

Objects are never {l|r|x}values. value categories describe expressions.

xvalue is the value category of the function call expression where the function return type is an rvalue reference to object (e.g. std::move), and it is also the value category of the cast expression where the cast is to an rvalue reference to object (e.g. the guts of std::move).

The function call expression createObject() in your example is an prvalue expression because it is a function call to a function with non-reference return type.

xvalue from a class member access expression?

There was also an example included in the answer but it did not exemplify(I guess) "a .* pointer-to-member expression in which the first operand is an xvalue and the second operand is a pointer to data member.", so could anyone please show me one?

Here's an example:

struct C { int m = 42; };

int C::* p = &C::m;
C&& get_xvalue();

std::cout << get_xvalue().*p; // get_xvalue() is an xvalue, p is a pointer to member

a class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue

This means that accessing non-static data member of non-reference type can have lvalue or xvalue value category depending on the value category of the object expression (i.e. of the part before the dot .). It will always be lvalue regardless, if the data member it is of reference type.

E.g.

struct C {
std::string x;
};

C obj;
std::move(obj.x) // rvalue and xvalue expression
std::move(obj).x // In this case xvalue since `x` is not a reference type. Otherwise it would have been lvalue

xvalues vs prvalues: what does identity property add

I personally have another mental model which doesn't deal directly with identity and memory and whatnot.

prvalue comes from "pure rvalue" while xvalue comes from "expiring value" and is this information I use in my mental model:

Pure rvalue refers to an object that is a temporary in the "pure sense": an expression for which the compiler can tell with absolute certainty that its evaluation is an object that is a temporary that has just been created and that is immediately expiring (unless we intervene to prolong it's lifetime by reference binding). The object was created during the evaluation of the expression and it will die according to the rules of the "mother expression".

By contrast, an expiring value is a expression that evaluates to a reference to an object that is promised to expire soon. That is it gives you a promise that you can do whatever you want to this object because it will be destroyed next anyway. But you don't know when this object was created, or when it is supposed to be destroyed. You just know that you "intercepted" it as it is just about to die.

In practice:

struct X;
auto foo() -> X;
X x = foo();
^~~~~

in this example evaluating foo() will result in a prvalue. Just by looking at this expression you know that this object was created as part of the return of foo and will be destroyed at the end of this full expression. Because you know all of these things you can prologue it's lifetime:

const X& rx = foo();

now the object returned by foo has it's lifetime prolongued to the lifetime of rx

auto bar() -> X&&
X x = bar();
^~~~

In this example evaluating bar() will result in a xvalue. bar promises you that is giving you an object that is about to expire, but you don't know when this object was created. It can be created way before the call to bar (as a temporary or not) and then bar gives you an rvalue reference to it. The advantage is you know you can do whatever you want with it because it won't be used afterwords (e.g. you can move from it). But you don't know when this object is supposed to be destroyed. As such you cannot extend it's lifetime - because you don't know what its original lifetime is in the first place:

const X& rx = bar();

this won't extend the lifetime.

Is this expression an xvalue?

My question is about the expression: std::move(x).var

Based on the text in the standard, I expect the expression to be an xvalue,

It is.

but the output is int, not int &&

That's because decltype comes in two forms. It can give information on how a name is declared, or it can either give information on the type and category of the expression.

Since std::move(x).var is a member access, you get the former. To get the latter, use decltype((std::move(x).var)) (with double parentheses).



Related Topics



Leave a reply



Submit