What Are Rvalues, Lvalues, Xvalues, Glvalues, and Prvalues

Real life examples of xvalues, glvalues, and prvalues?

Consider the following class:

class Foo
{
std::string name;

public:

Foo(std::string some_name) : name(std::move(some_name))
{
}

std::string& original_name()
{
return name;
}

std::string copy_of_name() const
{
return name;
}
};

The expression some_foo.copy_of_name() is a prvalue, because copy_of_name returns an object (std::string), not a reference. Every prvalue is also an rvalue. (Rvalues are more general.)

The expression some_foo.original_name() is an lvalue, because original_name returns an lvalue reference (std::string&). Every lvalue is also a glvalue. (Glvalues are more general.)

The expression std::move(some_name) is an xvalue, because std::move returns an rvalue reference (std::string&&). Every xvalue is also both a glvalue and an rvalue.


Note that names for objects and references are always lvalues:

std::string a;
std::string& b;
std::string&& c;

Given the above declarations, the expressions a, b and c are lvalues.

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]).

Some differences between xvalue and prvalue

foo2 is returning reference bound to temporary which is destroyed immediately; it always returns a dangled reference.

a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of the return expression. Such function always returns a dangling reference.

Dereference on the returned reference like foo2(bar1).value and bar2 = foo2(bar1); leads to UB; anything is possible.

On the other hand, foo1 doesn't have such issue. The return value is moved from the temporary object.

What does it mean xvalue has identity?

The xvalue does have an identity, but there's a separate rule in the language that a unary &-expression requires an lvalue operand. From [expr.unary.op]:

The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue [...]

You can look at the identity of an xvalue after performing rvalue-to-lvalue conversion by binding the xvalue to a reference:

int &&r = std::move(a);
int *p = &r; // OK

In C++, what categories (lvalue, rvalue, xvalue, etc.) can expressions that produce temporaries of class type fall into?

Every expression is one, and only one, of:

  • lvalue
  • xvalue
  • prvalue

The union of expressions that are lvalues and xvalues are known collectively as glvalues.

The union of expressions that are xvalues and prvalues are known collectively as rvalues.

Thus xvalue expressions are known both as glvalues and rvalues.

The handy diagram found in Alf's answer correctly illustrates the relationship I've described with words above, and is also found in section 3.10 of the C++ standards, versions C++11 and above.

Everything I've said above, I suspect the OP already knew, just from the wording of the title of this question.


Trivia:

Bjarne Stroustrup invented this classification of expressions, and in
doing so perhaps saved the entire rvalue reference proposal from
collapsing in the Core Working Group. I will be forever grateful.


What I'm adding is a way to discover for yourself which of the three bottom classification categories any expression falls into: lvalue, xvalue or prvalue.

#include <type_traits>
#include <typeinfo>
#include <iostream>
#ifndef _MSC_VER
# include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <typename T>
std::string
expression_name()
{
typedef typename std::remove_reference<T>::type TR;
std::unique_ptr<char, void(*)(void*)> own
(
#ifndef _MSC_VER
__cxxabiv1::__cxa_demangle(typeid(TR).name(), nullptr,
nullptr, nullptr),
#else
nullptr,
#endif
std::free
);
std::string r = own != nullptr ? own.get() : typeid(TR).name();
if (std::is_const<TR>::value)
r += "const ";
if (std::is_volatile<TR>::value)
r += "volatile ";
if (std::is_lvalue_reference<T>::value)
r = "lvalue expression of type " + r;
else if (std::is_rvalue_reference<T>::value)
r = "xvalue expression of type " + r;
else
r = "prvalue expression of type " + r;
return r;
}

The above function can be used like:

std::cout << "some_expression is a " << expression_name<decltype(some_expression)>() << '\n';

And it will answer this OP's question. For example:

int main()
{
std::cout << "Foo(5) is a " << expression_name<decltype(Foo(5))>() << '\n';
std::cout << "Foo(5).get_addr() is a " << expression_name<decltype(Foo(5).get_addr())>() << '\n';
std::cout << "Foo(78) = Foo(86) is a " << expression_name<decltype(Foo(78) = Foo(86))>() << '\n';
std::cout << "++Foo(5) is a " << expression_name<decltype(++Foo(5))>() << '\n';
std::cout << "++(Foo(78) + Foo(86)) is a " << expression_name<decltype(++(Foo(78) + Foo(86)))>() << '\n';
std::cout << "Foo(78) + Foo(86) is a " << expression_name<decltype(Foo(78) + Foo(86))>() << '\n';
std::cout << "std::move(Foo(5)) is a " << expression_name<decltype(std::move(Foo(5)))>() << '\n';
std::cout << "std::move(++Foo(5)) is a " << expression_name<decltype(std::move(++Foo(5)))>() << '\n';
}

For me prints out:

Foo(5) is a prvalue expression of type Foo
Foo(5).get_addr() is a prvalue expression of type void*
Foo(78) = Foo(86) is a lvalue expression of type Foo
++Foo(5) is a lvalue expression of type Foo
++(Foo(78) + Foo(86)) is a lvalue expression of type Foo
Foo(78) + Foo(86) is a prvalue expression of type Foo
std::move(Foo(5)) is a xvalue expression of type Foo
std::move(++Foo(5)) is a xvalue expression of type Foo

One area to be careful of in the use of this function:

decltype(variable_name) will give the declared type of the variable name. If you want to discover the value category of the expression when variable_name is used (as opposed to its declared type), then you need to add extra parentheses around (variable_name) when used in decltype. That is:

decltype((variable_name))

is the type of the expression variable_name, and not the declared type of variable_name.

For example given:

    Foo&& foo = Foo(5);
std::cout << "foo is a " << expression_name<decltype(foo)>() << '\n';

This will erroneously output:

foo is a xvalue expression of type Foo

Add the extra parentheses to the decltype:

    std::cout << "foo is a " << expression_name<decltype((foo))>() << '\n';

to convert foo from a type name into an expression. Now the output is:

foo is a lvalue expression of type Foo

If you are unsure whether you need to add parentheses or not to get the correct answer, then just add them. Adding them won't make a correct answer wrong -- unless you are looking to get the declared type of a variable, and not the type of an expression. And in that latter case, you want a closely related function: type_name<T>().



Related Topics



Leave a reply



Submit