Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@stavalfi
Last active November 27, 2021 20:33
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save stavalfi/52560f2b0d57d97b34ecae21f0bc9fa9 to your computer and use it in GitHub Desktop.
Save stavalfi/52560f2b0d57d97b34ecae21f0bc9fa9 to your computer and use it in GitHub Desktop.
C++ lvalue, rvalue and between By Stav Alfi

C++ lvalue, rvalue and between By Stav Alfi

Topics

  1. Introduction
  2. Object definitions
  3. Common misunderstanding
  4. Expressions vs Types
  5. Move consturctor and opertator
  6. Source
  7. Complete terms table
  8. Legal

Introduction

This tutorial attempts to clarify the common mistakes about the multiple invalid definitions for lvalue and rvalue expressions.

Object definitions

I will start by introducing the common, valid and different definitions for the term obejct. 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. 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).

Examples:

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.

End of examples

Common misunderstanding

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.

Expressions vs Types

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.

Examples:
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
end of examples

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.

Examples:
int f1() { return 6 };

6;      // prvalue
f1();   // prvalue
end of examples

Definition 2.3. A rvalue reference is a type (not an expression) of a special reference: T&& or const 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.

Examples:
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() ).
end of examples
Common mistake:

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.

End of common mistake

There are 4 ways to create an rvalue reference type:

  1. Return from a function a temporary object - int&& f1() { return 6; }
  2. Assigning a temporary object directrly to rvalue referencevaraible.
  3. static_cast<T&&>(....) - int&& x = static_cast<int&&>(6);
  4. 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:

  1. A call to a function which return a rvalue reference to an object of type T (not function).
  2. a convert to type of rvalue reference. (for example, static_cast<T&&>(...)).
  3. an access to xvalue expression's class member c. The evaluation of a xvalue expression must be an object of type T.
Examples:
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
end of examples

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.

Move constructor and operator:

Move constructor

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.

Examples:
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()
End of examples

What if we want to send the result of f1 to f2: f2(f1())? Then The stack, in this example, will looks like:

Examples
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())
End of examples

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.

Source:

Complete terms table

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&& or const T&&. An rvalue reference is a reference to temporary object.

Definition 2.4. A rvalue is an expression which is not a lvalue.

Legal

© 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment