Core C++ - lvalues and rvalues
Saturday, 27 February 2016
One of the most misunderstood aspect of C++ is the use of the terms lvalue and rvalue, and what they mean for how code is interpreted. Though lvalue and rvalue are inherited from C, with C++11, this taxonomy was extended and clarified, and 3 more terms were added: glvalue, xvalue and prvalue. In this article I'm going to examine these terms, and explain what they mean in practice.
Before we discuss the importance of whether something is an lvalue or rvalue, let's take a look at what makes an expression have each characteristic.
The Taxonomy
The result of every C++ expression is either an lvalue, or an rvalue. These terms come from C, but the C++ definitions have evolved quite a lot since then, due to the greater expressiveness of the C++ language.
rvalues can be split into two subcategories: xvalues, and prvalues, depending on the details of the expression. These subcategories have slightly different properties, described below.
One of the differences is that xvalues can sometimes be treated the same as lvalues. To cover those cases we have the term glvalue — if something applies to both lvalues and xvalues then it is described as applying to glvalues.
Now for the definitions.
glvalues
A glvalue is a Generalized lvalue. It is used to refer to something that could be either an lvalue or an xvalue.
rvalues
The term rvalue is inherited from C, where rvalues are things that can be on the Right side of an assignment. The term rvalue can refer to things that are either xvalues or prvalues.
lvalues
The term lvalue is inherited from C, where lvalues are things that can be on the Left side of an assignment.
The simplest form of lvalue expression is the name of a variable. Given a variable declaration:
A v1;
The expression v1
is an lvalue of type A
.
Any expression that results in an lvalue reference (declared with &
) is also
an lvalue, so the result of dereferencing a pointer, or calling a function
that returns an lvalue reference is also an lvalue. Given the following
declarations:
A* p1;
A& r1=v1;
A& f1();
The expression *p1
is an lvalue of type A
, as is the expression f1()
and
the expression r1
.
Accessing a member of an object where the object expression is an lvalue is also an lvalue. Thus, accessing members of variables and members of objects accessed through pointers or references yields lvalues. Given
struct B{
A a;
A b;
};
B& f2();
B* p2;
B v2;
then f2().a
, p2->b
and v2.a
are all lvalues of type A
.
String literals are lvalues, so "hello"
is an lvalue of type array of 6
const char
s (including the null terminator). This is distinct from other
literals, which are prvalues.
Finally, a named object declared with an rvalue reference (declared with &&
)
is also an lvalue. This is probably the most confusing of the rules, if for no
other reason than that it is called an rvalue reference. The name is just
there to indicate that it can bind to an rvalue (see later); once you've
declared a variable and given it a name it's an lvalue. This is most commonly
encountered in function parameters. For example:
void foo(A&& a){
}
Within foo
, a
is an lvalue (of type A
), but it will only bind to rvalues.
xvalues
An xvalue is an eXpiring value: an unnamed objects that is soon to be destroyed. xvalues may be either treated as glvalues or as rvalues depending on context.
xvalues are slightly unusual in that they usually only arise through explicit
casts and function calls. If an expression is cast to an rvalue reference to
some type T
then the result is an xvalue of type
T
. e.g. static_cast<A&&>(v1)
yields an xvalue of type A
.
Similarly, if the return type of a function is an rvalue reference to some
type T
then the result is an xvalue of type T
. This is the case with
std::move()
, which is declared as:
template <typename T>
constexpr remove_reference_t<T>&&
move(T&& t) noexcept;
Thus std::move(v1)
is an xvalue of type A
— in this case, the type
deduction rules deduce T
to be A&
since v1
is an lvalue, so
the return type is declared to be A&&
as remove_reference_t<A&>
is just A
.
The only other way to get an xvalue is by accessing a member of an
rvalue. Thus expressions that access members of temporary objects
yield xvalues, and the expression B().a
is an xvalue of type A
, since
the temporary object B()
is a prvalue. Similarly,
std::move(v2).a
is an xvalue, because std::move(v2)
is an xvalue, and
thus an rvalue.
prvalues
A prvalue is a Pure rvalue; an rvalue that is not an xvalue.
Literals other than string literals (which are lvalues) are
prvalues. So 42
is a prvalue of type int
, and 3.141f
is a prvalue
of type float
.
Temporaries are also prvalues. Thus given the definition of A
above, the
expression A()
is a prvalue of type A
. This applies to all temporaries:
any temporaries created as a result of implicit conversions are thus also
prvalues. You can therefore write the following:
int consume_string(std::string&& s);
int i=consume_string("hello");
as the string literal "hello"
will implicitly convert to a temporary of type
std:string
, which can then bind to the rvalue reference used for the
function parameter, because the temporary is a prvalue.
Reference binding
Probably the biggest difference between lvalues and rvalues is in how they bind to references, though the differences in the type deduction rules can have a big impact too.
There are two types of references in C++: lvalue references, which are
declared with a single ampersand, e.g. T&
, and rvalue references which are
declared with a double ampersand, e.g. T&&
.
lvalue references
A non-const
lvalue reference will only bind to non-const
lvalues of the same type, or a class derived from the referenced
type.
struct C:A{};
int i=42;
A a;
B b;
C c;
const A ca{};
A& r1=a;
A& r2=c;
//A& r3=b; // error, wrong type
int& r4=i;
// int& r5=42; // error, cannot bind rvalue
//A& r6=ca; // error, cannot bind const object to non const ref
A& r7=r1;
// A& r8=A(); // error, cannot bind rvalue
// A& r9=B().a; // error, cannot bind rvalue
// A& r10=C(); // error, cannot bind rvalue
A const
lvalue reference on the other hand will also bind to
rvalues, though again the object bound to the reference must have
the same type as the referenced type, or a class derived from the referenced
type. You can bind both const
and non-const
values to a const
lvalue
reference.
const A& cr1=a;
const A& cr2=c;
//const A& cr3=b; // error, wrong type
const int& cr4=i;
const int& cr5=42; // rvalue can bind OK
const A& cr6=ca; // OK, can bind const object to const ref
const A& cr7=cr1;
const A& cr8=A(); // OK, can bind rvalue
const A& cr9=B().a; // OK, can bind rvalue
const A& cr10=C(); // OK, can bind rvalue
const A& cr11=r1;
If you bind a temporary object (which is a prvalue) to a const
lvalue reference, then the lifetime of that temporary is extended to the
lifetime of the reference. This means that it is OK to use r8
, r9
and r10
later in the code, without running the undefined behaviour that would otherwise
accompany an access to a destroyed object.
This lifetime extension does not extend to references initialized from the first
reference, so if a function parameter is a const
lvalue reference, and gets bound
to a temporary passed to the function call, then the temporary is destroyed when
the function returns, even if the reference was stored in a longer-lived
variable, such as a member of a newly constructed object. You therefore need to
take care when dealing with const
lvalue references to ensure that you
cannot end up with a dangling reference to a destroyed temporary.
volatile
and const volatile
lvalue references are much less interesting,
as volatile
is a rarely-used qualifier. However, they essentially behave as
expected: volatile T&
will bind to a volatile
or non-volatile
, non-const
lvalue of type T
or a class derived from T
, and volatile const T&
will bind to any lvalue of type T
or a class derived from
T
. Note that volatile const
lvalue references do not bind to rvalues.
rvalue references
An rvalue reference will only bind to rvalues of the same
type, or a class derived from the referenced type. As for lvalue references,
the reference must be const
in order to bind to a const
object, though
const
rvalue references are much rarer than const
lvalue references.
const A make_const_A();
// A&& rr1=a; // error, cannot bind lvalue to rvalue reference
A&& rr2=A();
//A&& rr3=B(); // error, wrong type
//int&& rr4=i; // error, cannot bind lvalue
int&& rr5=42;
//A&& rr6=make_const_A(); // error, cannot bind const object to non const ref
const A&& rr7=A();
const A&& rr8=make_const_A();
A&& rr9=B().a;
A&& rr10=C();
A&& rr11=std::move(a); // std::move returns an rvalue
// A&& rr12=rr11; // error rvalue references are lvalues
rvalue references extend the lifetime of temporary objects in the same way
that const
lvalue references do, so the temporaries associated with rr2
,
rr5
, rr7
, rr8
, rr9
, and rr10
will remain alive until the
corresponding references are destroyed.
Implicit conversions
Just because a reference won't bind directly to the value of an expression, doesn't mean you can't initialize it with that expression, if there is an implicit conversion between the types involved.
For example, if you try and initialize a reference-to-A
with a D
object, and
D
has an implicit conversion operator that returns an A&
then all is well,
even though the D
object itself cannot bind to the reference: the reference is
bound to the result of the conversion operator.
struct D{
A a;
operator A&() {
return a;
}
};
D d;
A& r=d; // reference bound to result of d.operator A&()
Similarly, a const
lvalue-reference-to-E
will bind to an A
object if A
is implicitly convertible to E
, such as with a conversion constructor. In this
case, the reference is bound to the temporary E
object that results from the
conversion (which therefore has its lifetime extended to match that of the
reference).
struct E{
E(A){}
};
const E& r=A(); // reference bound to temporary constructed with E(A())
This allows you to pass string literals to functions taking std::string
by
const
reference:
void foo(std::string const&);
foo("hello"); // ok, reference is bound to temporary std::string object
Other Properties
Whether or not an expression is an lvalue or rvalue can affect a few other aspects of your program. These are briefly summarised here, but the details are out of scope of this article.
In general, rvalues cannot be modified, nor can they have their
address taken. This means that simple expressions like A()=something
or &A()
or &42
are ill-formed. However, you can call member functions on
rvalues of class type, so X().do_something()
is valid (assuming
class X
has a member function do_something
).
Class member functions can be tagged with ref qualifiers to indicate that they
can be applied to only rvalues or only lvalues. ref
qualifiers can be combined with const
, and can be used to distinguish
overloads, so you can define different implementations of a function depending
whether the object is an lvalue or rvalue.
When the type of a variable or function template parameter is deduce from its initializer, whether the initializer is an lvalue or rvalue can affect the deduced type.
End Note
rvalues and lvalues are a core part of C++, so understanding them is essential. Hopefully, this article has given you a greater understanding of their properties, and how they relate to the rest of C++.
If you liked this article, please share with the buttons below. If you have any questions or comments, please submit them using the comment form.
Posted by Anthony Williams
[/ cplusplus /] permanent link
Tags: cplusplus, lvalues, rvalues
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Design and Content Copyright © 2005-2024 Just Software Solutions Ltd. All rights reserved. | Privacy Policy
14 Comments
Thanks For Giving Us Some Important Information About <a href="www.itenterprise.co.uk ">Software Development</a> this Blog Is Very Helpful on Software Solution.
In the code example of "rvalue references", the comment of last line is "error rvalue references are [*lvalue*s](#lvalue)". How to understand that "rvalue reference is lvalue"?
How to write if I want to assign a rvalue reference to another rvalue reference?
Named rvalue references are lvalues, as explained in the lvalue section. To assign it to another rvalue reference you need to convert it to an rvalue, which you can do by casting or using std::move, as described in the xvalue section.
Great post, no matter how much time I spend on modern C++, I'm always glad to refresh these concepts in my head. I just got one doubt though: In the section regarding "void foo (A &&a)", you say "Within foo, a is an lvalue (of type A), but it will only bind to rvalues.". Wouldn't "a" really be an lvalue of type A&&, instead of type A? Don't think it makes a lot of difference in this context as we're more interested in the L/R-valueness of the expression, and that's an lvalue for sure, but I'd like to know if I've still some rough edges to iron out in my mental model...
Andrea: No. That way lies confusion. Values are not references. The reference-ness of a declaration only informs the lvalue/rvalue-ness of the resulting expression, it does not form part of the expression type.
Anthony: Could you elaborate a little more on your last comment?
I would have agreed with Andrea in that after "void foo (A &&a)", "a" is a function parameter of type "rvalue reference to A". This type is different from type A, and you can easily tell the difference from a function parameter declared with type A. Nevertheless, expression "a" is an lvalue by the C++ language specification - although it might be counterintuitive to use the word "value" when we talk about a reference. Please correct me if I got something wrong!
Stefan: Yes, you are right.
The parameter "a" is of type "A&&", and decltype(a) will confirm that.
However, the *expression* "a" is an lvalue of type "A". The reference-ness confers lvalue-ness, but that is all it does at this point. From the point-of-view of which operations you can perform, and which overloads will be chosen, "A& a", "A&& a" and "A a" are all the same: the expression "a" is an lvalue of type "A".
Anthony: With regard to overload resolution, lvalue arguments of types "A&", "A&&", and "A" of course all behave the same.
However, according to 5.1.1(8) of the standard, it seems that the type of expression "a" is still the type of variable/parameter "a" (i.e., A&& in our case). Maybe this is why we misunderstood what you meant by "lvalue of type A":
5.1.1(8): An identifier is an id-expression provided it has been suitably declared. ... The type of the expression is the type of the identifier.
I'm sorry for the hairsplitting. Please be assured that I really enyojed your post!
In your post you say that an xvalue has no name but
http://en.cppreference.com/w/cpp/language/value_category
says otherwise. It does indeed have identity but will be soon expiring via cast (etc.). Either that or I am not reading something.
For what I can tell lvalue and xvalue are both glvalues with identity except lvalue cannot be moved while xvalue can and soon will be moved. That is why it makes sense that both have identities/addresses. If you say an xvalue can be moved and also does not have a name that is pretty much a pure r value (RPVALUE) since an rvalue that can be named is an xvalue.
??? Sincerely, RVP
@RVP: "has identity" is not the same as "has a name". The expression "static_cast<A&&>(A())" is an xvalue of type A, but it has no name.
An xvalue is the result of a cast expression or function call, not the evaluation of an identifier. It might refer to the same object as another expression, and that object might have a name, but the xvalue does not itself have a name.
Yes, xvalues are glvalues.
Most of the time you do indeed create an xvalue in order to move the object, but it is not a requirement.
You can create an xvalue that refers to a temporary (which is itself a prvalue), e.g. static_cast<A&&>(A()).
glvalue, xvalue and prvalue are not invented in C++11.
@Ying: Sure? Here is a write-up by Bjarne Stroustrup about how they invented the terminology: www.stroustrup.com/terminology.pdf
std::list<class A> ACollections; ... for(auto&& item : ACollections) { ... }
The item is a rvalue or lvalue? it looks like the item has a name, although it has &&, it should be lvalue. This is correct?