# Value Categories (L-Values and R-Values)

A straightforward guide to l-values and r-values, aimed at helping you understand the fundamentals
This lesson is part of theÂ course:

### Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
###### Ryan McCombe
Updated

In the previous lesson on move semantics, we introduced a new type of reference, which uses the &&Â syntax:

#include <iostream>

struct Resource {
// Copy Constructor
Resource(const Resource& Source) {}

// Move Constructor
Resource(Resource&& Source) {}
};

In this lesson, weâ€™ll explore what this means in more detail, by introducing value categories.

## L-Values and R-Values

Weâ€™ve seen plenty of examples of expressions so far. Expressions are blocks of syntax that produce some value. ExamplesÂ include:

• 42 - a literal value
• SomeVariable - a variable
• SomeFunction() - the value returned by a function call
• SomeVariable + 42 - the value returned by an operator

We can broadly consider these values as belonging to one of two categories - left values (l-values) and right values (r-values). Weâ€™ll cover the meaning of the "left" and "right" a little later in thisÂ section.

### L-Values

An l-value is an expression that identifies a specific object or function. If we can identify the address of an expression using the & operator, then it is anÂ l-value.

In the previous list, SomeVariable is an l-value, as we can get its address using &SomeVariable. For this reason, the "l" in l-values is sometimes considered to refer to locator values.

### R-Values

An r-value is anything that is not an l-value - that is, anything that is not identifiable. In the previous example, 42 is an r-value - attempting to get its address using &42 will result in a compilationÂ error.

The other examples in the list - SomeFunction() and SomeVariable + 42 - could be either l-values or r-values. It depends on what the function/operator returns. Weâ€™ll discuss the value categories of functions and operators later in thisÂ section.

## Binding R-Values to L-Values

A simplification that helps to explain the difference is to imagine l-values are containers, and r-values are the contents of the container. In the following example, x is an l-value, and 5 is anÂ r-value:

int x { 5 };

Without a container, an r-value doesnâ€™t last long. Typically, it is lost right after the expression it is used in is evaluated. In the following example, 1 and 5 areÂ r-values:

int main() {
1 + 5;
}

The combined expression 1 + 5 is also an r-value. The result of that expression (6) is discarded the moment it is generated unless we store it in an identifiable place, represented by anÂ l-value:

int main() {
int Result{1 + 5};
}

## L-Value to R-Value Conversions

The reason the value categories are called "left values" and "right values" is based on where they occur in relation to the equality operator =. An l-value like MyVariable appears on the left, whilst an r-value like 42 appears on theÂ right:

int main() {
int MyVariable = 42;
}

This is true, but can be immediately confusing, as weâ€™ve seen countless examples where we have an l-value on the right side of the equalityÂ operator:

int main() {
int Number{42};
int MyVariable = Number;
}

This works because the compiler can implicitly convert an l-value to an r-value when it is used in an r-value context, such as on the right side of the assignmentÂ operator.

Just like we can use a float in a scenario where an int is expected, we can use an l-value in a situation where an r-value isÂ required.

Behind the scenes, the compiler takes care of it for us. The opposite is not true - we cannot use an r-value in a place where an l-value isÂ expected:

int main() {
int MyVariable{42};

42 = MyVariable;
}
error C2106: '=': left operand must be l-value

## Functions and Operators

Function names are l-values, as we can get their memory address using the &Â operator:

#include <iostream>

void SomeFunction(){};

int main() {
std::cout << &SomeFunction;
}
00007FF7871819D8

This creates a function pointer - we discuss the applications of function pointers later in theÂ course:

Whilst a function name is an l-value, the result of a function call such as SomeFunction() could be an l-value or an r-value. It depends on what the function returns. In most cases, a function will be returning an r-value, so an expression like SomeFunction() is also anÂ r-value.

As such, it doesnâ€™t have an identifiable memory address, so using the & operator will generate a compilerÂ error:

#include <iostream>

int GetNumber(){ return 42; };

int main() {
// GetNumber is an l-value, so this is valid
std::cout << &GetNumber;

// GetNumber() is an r-value, so this is invalid
std::cout << &GetNumber();
}
error C2102: '&' requires l-value

However, a function invocation is not always an r-value. Functions can return l-values, so an expression like SomeFunction() could be an l-value. For example, a function can return a reference to some object in memory, which is anÂ l-value.

Below, our GetNumber() function returns a reference to an l-value in our global scope, which has a memory address we can retrieve using the &Â operator:

#include <iostream>

int Number{42};

int& GetNumber(){ return Number; };

int main() {
// GetNumber is an l-value
std::cout << &GetNumber << '\n';

// GetNumber() is an l-value
std::cout << &GetNumber();
}
00007FF6E9A819E2
00007FF6E9A9F018

Operators are also functions, so the same logic applies. Below, our + operator returns an r-value - an instance of SomeType. Like any r-value, this will be lost as soon as our expression ends, unless we bind it to anÂ l-value.

However, the ++ operator returns an l-value - specifically, it returns the object the ++ was called on. In the following example, that will be the l-value we called Object:

#include <iostream>

struct SomeType {
SomeType operator+(int x) {
return SomeType {Value + x};
}

SomeType& operator++() {
++Value;
return *this;
}

int Value;
};

int main() {
SomeType Object;
// Object + 42 is an r-value
std::cout << &(Object + 42);

// ++Object is an l-value
std::cout << &(++Object);
}
error C2102: '&' requires l-value

## L-Value References

Within our functions parameters, we previously saw how we could denote references using the &Â character:

void SomeFunction(int &x) {}

Whilst we didnâ€™t draw attention to this nuance in the past, what weâ€™re creating here is specifically an l-value reference. If we attempt to pass an r-value into this parameter, the compiler would have thrown anÂ error:

void SomeFunction(int &x) {}

int main() {
SomeFunction(5);
}
no matching function for call to 'SomeFunction'
candidate function not viable: expects an lvalue for 1st argument

This makes conceptual sense - by our function accepting a non-const reference, we are expressing a desire to modify an argument. But that doesnâ€™t make sense when our argument is an r-value. The r-value will be lost as soon as our SomeFunction(5) expression ends, so any modifications we make would beÂ pointless

If we donâ€™t intend to modify the object and are instead passing by reference for performance reasons, we can specify our parameter as being const and our code will compileÂ successfully:

void SomeFunction(const int &x) {}

int main() {
SomeFunction(5);
}

## R-Value References

To create an r-value reference, we annotate our type with an additional &. ForÂ example:

• int& is an l-value reference to an int
• int&& is an r-value reference to an int

This allows us to provide different implementations of a function, depending on whether an argument is an l-value reference or an r-valueÂ reference:

#include <iostream>

void SomeFunction(int& x) {
std::cout << "That was an l-value\n";
}

void SomeFunction(int&& x) {
std::cout << "That was an r-value\n";
}

int main() {
int x{2};
SomeFunction(x);
SomeFunction(5);
}
That was an l-value
That was an r-value

Our previous lesson on move semantics introduced the main practical use for this. A copy constructor accepts an l-value reference, whilst a move constructor accepts an r-valueÂ reference:

#include <iostream>

struct Resource {
// Default constructor
Resource() {}

// l-value reference
Resource(const Resource& Source) {
std::cout << "Copying resource\n";
}

// r-value reference
Resource(Resource&& Source) {
std::cout << "Moving resource\n";
}
};

int main() {
Resource Original;
Resource A{Original};
Resource B{std::move(Original)};
}
Copying resource
Moving resource

The properties of l-value and r-value expressions we covered at the beginning of this lesson link up to how we treat source objects within copy and moveÂ semantics:

• An l-value is considered more "important" than an r-value, because it has a longer lifespan. It survives after the expression in which it is used. Copy sementics therefore receive their source object as an l-value reference (MyType& Source) indicating that it needs to respect it and preserve its subresources.
• An r-value is considered less important because it has a shorter lifespan. Move semantics receive their source objects as r-value references (eg MyType&& Source). This indicates we are free to just take control of its subresources, because the object is expiring soon anyway.

## What std::move() really does

With an understanding of l-values and r-values under our belt, we can go a little deeper into what std::move() actually does. At this point, itâ€™s perhaps clear that std::move() doesnâ€™t actually moveÂ anything.

Instead, it indicates that the object may be moved from, enabling move semantics if available. In other words, it casts its argument to an r-value reference, which can influence what function is selected when that reference appears in the argumentÂ list:

#include <iostream>

void SomeFunction(int& x) {
std::cout << "That was an l-value\n";
}

void SomeFunction(int&& x) {
std::cout << "That was an r-value\n";
}

int main() {
int x{2};

// This calls the l-value variation
SomeFunction(x);

// This calls the r-value variation
SomeFunction(std::move(x));
}
That was an l-value
That was an r-value

Within our move semantics example, we could get the exact same behaviour using static_cast instead of std::move(), which we demonstrateÂ below:

#include <iostream>

struct Resource {/*...*/};

int main() {
Resource Original;
Resource A{Original};
Resource B{static_cast<Resource&&>(Original)};
}
Copying resource
Moving resource

If our intent for casting to an r-value reference is related to move semantics (which it typically is), we should prefer to use the std::move() technique. It reduces the amount of syntax, making our code easier to read, and it also makes our reason for performing the cast more obvious to thoseÂ readers.

## Summary

In this lesson, we delved into l-values and r-values, exploring their definitions, distinctions, and how they interact with C++'s type system. The key takeawaysÂ are:

• The difference between l-values and r-values.
• How to identify l-values and r-values, with l-values being addressable expressions and r-values being non-addressable or temporary values.
• The concept of binding r-values to l-values and the transient nature of r-values unless they are assigned to an identifiable l-value.
• Functions and operators can return either l-values or r-values.
• Introduction to l-value and r-value references, highlighting their interactions with function overloading and move semantics.
• The std::move() function casts its argument to an r-value reference. It is simply a friendly and more more descriptive way of implementing an equivalent static_cast expression.

Next Lesson

### Type Aliases

Learn how to use type aliases, using statements, and typedef to simplify or rename complex C++ types.
New: AI-Powered AssistanceAI Assistance

### Questions and HelpNeed Help?

Get instant help using our free AI assistant, powered by state-of-the-art language models.

Updated
Lesson Contents

### Value Categories (L-Values and R-Values)

A straightforward guide to l-values and r-values, aimed at helping you understand the fundamentals

This lesson is part of theÂ course:

### Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access

###### Value Categories (L-Values and R-Values)

A straightforward guide to l-values and r-values, aimed at helping you understand the fundamentals

This lesson is part of the course:

## Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

### This course includes:

• 124 Lessons
• 550+ Code Samples
• 96% Positive Reviews
• Regularly Updated
• Help and FAQ
Next Lesson

### Type Aliases

Learn how to use type aliases, using statements, and typedef to simplify or rename complex C++ types.