- Introduction
- Object definitions
- Common misunderstanding
- Expressions vs Types
- Move consturctor and opertator
- Source
- Complete terms table
- Legal
This tutorial attempts to clarify the common mistakes about the multiple invalid definitions for lvalue
and rvalue
expressions.
I will start by introducing the common, valid and different definitions for the term object
.
Throughout this tutorial, I will implicitly use the first definition.
Definition 1.1. An object in c programing language is an area in the memory which contain an address we can reach which may contain data.
For example, function or an instance of a struct.
Definition 1.2. An object in OPP is an instance of a class.
Also I will define one more common term, I will use alot:
Definition 1.3. A temporary object is an object of class type. it does not have a name (we can't reach this object after it's evaluation). It has an address on the stack and it will be destoryed after evaluating the full expression which which has a "some link" to the expression who created that temporary object, unless we named that temporary object. By naming I mean, creating a "way" to reach that temporary object which the compiler approve (This tutorial will teach you how to do it).
From definition 1.3: "it will be destoryed after evaluating the expression which created him...":
struct A {
A(){
cout<<"A::A()"<<endl;
}
A(const A& a){
cout<<"A::A(const A& a)"<<endl;
}
~A(){
cout<<"A::~A()"<<endl;
}
};
A f1(){
return A(); // return a temporary object
}
A f2(A a) {
cout<<"f2"<<endl;
return a;
}
int main() {
f2(f2(f1()));
return 0;
}
Stack trace:
A::A()
A::A(const A& a)
A::~A()
A::A(const A& a)
f2
A::A(const A& a)
A::A(const A& a)
f2
A::A(const A& a)
A::~A()
A::~A()
A::~A()
A::~A()
A::~A()
As you can see, all the temporary objects are destoryed after evaluating the full expression. Also I will add to the denition above that the order they are detoryed is opposite to the order they were created.
Note: to compile the above code, please add -fno-elide-constructors
flag to the g++ compilation command to avoid comipler's consturctor optimizations.
The standart explicitly say:
Historically, lvalues and rvalues were so-called because they could appear on the left- and right-hand side of an assignment (although this is no longer generally true); ... Despite their names, these terms classify expressions, not values.
To better understand what are lvalue
and rvalue
expressions, lets define the following expressions:
Definition 2.1. A glvalue is an expression which it's evaluation is an object, function or bitfield which we can choose (we can also choose not)_ to refer later (later is equal to - after the evaluation of the given expression).
By definition, any expressions which it's evaluation is a reference to object type, are also glvalue expression.
int f(); // glvalue - this expression is a function. by defenition, this expression is a glvalue.
&f; // glvalue - this expression evaluates a pointer which we can refer to later.
// by defenition, this expression is a glvalue.
f(); // not glvalue - this expression evaluates a call to function f which return a temporary value.
// we can't refer to that specific temporary value because after the function will finish executiong,
// the temporary object will be destoryed.
class A{
int k;
const A& f1() { return A();} // compiler warning: returning reference to temporary.
const int& f2() { return k;} // note: k is glvalue.
};
A a;
a.f1(); // it doesn't matter if this expression is a glvalue or not.
// it's undefined - can't use the returned object. it already destoryed.
a.f2(); // glvalue - this expression return a const reference to k. k can be accessed (refer to) after evaluating this expression.
const int x=4;
x; // glvalue - x can be accessed after executing this expression.
int i;
int* p = &i;
int& f();
int&& g(); // note: T&& is eventually a reference to object of type T. * We will talk about how is it different from T& soon. *
int h();
h(); // not glvalue
g(); // glvalue
f(); // glvalue
i; // glvalue
*p; // glvalue
We noticed that a glvalue
expression evaluate object or reference which can sometimes be assigned too and always can be read from.
glvalue
expression is a lvalue
or a xvalue
expression only.
To better understand what is a xvalue
expression, first we need to understand what are prvalue
expression and rvalue reference
type.
Definition 2.2. A prvalue (pure rvalue) is an expression which it's evaluation is a temporary object.
int f1() { return 6 };
6; // prvalue
f1(); // prvalue
Definition 2.3. A rvalue reference is a type (not an expression) of a special reference:
T&&
orconst T&&
. An rvalue reference is a reference to temporary object.
Important note: As soon as there are no active references to that temporary object (it can happen after the end of scope of all rvalue references
to this temporary object), this temporary object will be destoryed. We will use this note later on.
const int&& f1() { return 6; } // f1 return type is rvalue reference.
A&& f2() { return A(); } // f2 return a reference to prvalue.
int&& x=f1(); // x is a reference. to be more precise - it is a rvalue reference
// (soon we will be able to answer what excatly is the expression f() ).
False claim: if expression e
is a rvalue reference
expression,
then e
is a lvalue
expression.
Contradiction: rvalue reference
is a type and not an expression.
There are 4 ways to create an rvalue reference
type:
- Return from a function a temporary object -
int&& f1() { return 6; }
- Assigning a temporary object directrly to
rvalue reference
varaible. static_cast<T&&>(....)
-int&& x = static_cast<int&&>(6);
std::move<T>(....)
-int&& x = std::move<int&&>(6);
std::move
source code:
/**
* @brief Convert a value to an rvalue.
* @param __t A thing of arbitrary type.
* @return The parameter cast to an rvalue-reference to allow moving it.
*/
template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
Definition 3.3. A recursive definition to xvalue is a glvalue expression which is one of the following forms:
- A call to a function which return a rvalue reference to an object of type T (not function).
- a convert to type of rvalue reference. (for example,
static_cast<T&&>(...)
).- an access to xvalue expression's class member
c
. The evaluation of a xvalue expression must be an object of type T.
struct A{ int data; };
int&& i;
int& f();
int&& g();
A&& g1();
int h();
h(); // not glvalue - rvalue
g(); // glvalue - xvalue
g().data; // glvalue - xvalue
f(); // glvalue - lvalue
i; // glvalue - lvalue
const int&& f1() { return 6; } // f1 return rvalue reference - a reference to rvalue expression.
int&& x=f1();
x; // glvalue - lvalue
f1(); // glvalue - xvalue
Definition 4.1. A rvalue is an expression which is not a lvalue.
Note: A xvalue expression can be considered to be glvlue
and rvalue
expressions.
There are 2 ways to create rvalue
expression: expression which it's evaluation is xvalue
or prvalue
. We will see now that there is an implicit cast from prvalue
expression's evaluation (which is an object) to oject of type rvalue reference
.
As we saw earlier, we can't modify a temporary objects or even modify a non-static member of a class though prvalue
expressions. Trying to do so, will lead to compilation error: using temporary as lvalue
.
struct A {
int* bigArray;
A():bigArray(new int[2^64]) {
cout<<"A::A()"<<endl;
}
A(const A& a) {
cout<<"A::A(const A& a)"<<endl;
// ...deep copy bigArray member....
}
~A() {
cout<<"A::~A()"<<endl;
delete this->bigArray;
}
};
A f1() {
cout<<"f1()"<<endl;
return A();
}
void f2(A a) {
cout<<"f2(A a)"<<endl;
}
int main() {
f1().bigArray=new int[4]; // compilation error: using temporary as lvalue
cout<<"finsihed f1().bigArray=new int[4]"<<endl;
...
}
Stack trance (incase there was no error):
f1()
A::A()
A::A(const A& a)
A::~A()
finsihed f1().bigArray=new int[4]
A::~A()
What if we want to send the result of f1
to f2
: f2(f1())
? Then The stack, in this example, will looks like:
int main() {
f2(f1());
cout<<"end of f2(f1());"<<endl;
...
}
Stack trance:
f1()
A::A()
A::A(const A& a)
A::~A()
A::A(const A& a)
f2(A a)
A::~A()
A::~A()
end of f2(f1())
Well, calling twise to A::copy constructor is unacceptable for us. Why? Because we are deep-copy 2 times the member bigArray
insead of zero times.
Why we would want to avoid, _ in this case_, deep-copy (2 times) the member bigArray
? Because f1()
is a prvalue
expression so it means we can't modify it's evaluation (which is an object). so I would like to, magicaly, send this object, which we can'y modify, directly to the parameter of f2
so f2
will use this object, which we can't modify, and let f2
use it as it want and even modify it by dark-megic! By doing so, we avoided creating 2 extra objects, we don't need. One of them is a temporary object it self which will be created in the main
: f2( -->>here<<--)
.
How do we accomplish it? By creating additional consturctor: move consturctor in A
class:
A(A&& a) {
cout<<"A::A(A&& a)"<<endl;
this->bigArray=a.bigArray;
// when the destructor of the temporary object which `a` is
// referencing to is called, our bigArray won't be deleted.
a.bigArray=nullptr;
}
Thats it!
f1()
A::A()
A::A(A&& a)
A::~A()
A::A(A&& a)
f2(A a)
A::~A()
A::~A()
end of f2(f1());
Same as for operator move.
Definition 1.1. An object in c programing language is an area in the memory which contain an address we can reach which may contain data.
Definition 1.2. An object in OPP is an instance of a class.
Definition 1.3. An temporary object is an object of class type. it does not have a name (we can't reach this object after it's evaluation). It has an address on the stack and it will be destoryed after evaluating the full expression which which has a "some link" to the expression who created that temporary object, unless we named that temporary object. By naming I mean, creating a "way" to reach that temporary object which the compiler approve (This tutorial will teach you how to do it).
Definition 2.1. A glvalue is an expression which it's evaluation (not the actual value it evaluates) is an object, function or bitfield which we can choose (we can also choose not) to refer later (later is equal to - after the evaluation of the given expression).
Definition 2.2. A prvalue (pure rvalue) is an expression which it's evaluation is a temporary object. A temporary object can't be refer to after we evaluate it.
Definition 2.3. A rvalue reference is a type (not an expression) of a special reference:
T&&
orconst T&&
. An rvalue reference is a reference to temporary object.
Definition 2.4. A rvalue is an expression which is not a lvalue.
© Stav Alfi, 2017. Unauthorized use and/or duplication of this material without express and written permission from the owner is strictly prohibited. Excerpts and links may be used, provided that full and clear credit is given to Stav Alfi with appropriate and specific direction to the original content.
Creative Commons License "C++ lvalue, rvalue and between By Stav Alfi" by Stav Alfi is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License. Based on a work at https://gist.github.com/stavalfi/52560f2b0d57d97b34ecae21f0bc9fa9.