An R-value reference is a reference capable of binding to temporary values, i.e., objects that are usually about to disappear.
R-value references are written with && and are one of the foundations of modern C++. Thanks to them, the language can distinguish between objects we must preserve and objects whose resources we can move.
This article continues what we saw with L-values and R-values. If that distinction still feels a bit fuzzy, that’s fine—we’ll use it practically here.
Quick Reminder
In C++, an expression can behave as an L-value or an R-value.
A L-value is something with identity: a named variable, a recognizable memory location, something that still exists after the expression.
int x = 10; // x is an L-value
x = 20; // we can assign another value to itAn R-value is usually a temporary value: a literal, the result of an operation, or an object that will be destroyed soon.
int y = x + 5; // x + 5 is an R-valueThis difference allows us to tell the compiler: this is stable, copy it carefully or this is temporary, you can take advantage of its resources.
Declaring an R-value Reference
An R-value reference is declared with two ampersands (&&).
int&& ref = 42;
ref += 8;
std::cout << ref << "\n"; // 50Although it may seem odd, ref has a name. Therefore, inside the code, ref behaves as an L-value. This is important because it’s one of those details that initially seems like a bad joke.
void procesar(int&& valor) {
// valor has a name, so here it behaves as an L-value
}Overloading Functions with & and &&
R-value references allow us to create different functions for persistent objects and temporary objects.
#include <iostream>
void procesar(int& valor) {
std::cout << "L-value: " << valor << "\n";
}
void procesar(int&& valor) {
std::cout << "R-value: " << valor << "\n";
}
int main() {
int x = 10;
procesar(x); // L-value
procesar(20); // R-value
procesar(x + 5); // R-value
}This is the gateway to move semantics. If we receive an R-value, we know we can likely move its resources instead of copying them.
Relationship with std::move
std::move does not move anything by itself. What it does is convert an expression into an R-value, allowing move constructors or move operators to be called.
#include <string>
#include <utility>
std::string origen = "texto largo";
std::string destino = std::move(origen);After std::move, origen remains a valid object, but its content is left in a valid but unspecified state. You can destroy it or assign a new value to it, but you should not assume what it contains.
Do not use std::move out of habit. Use it when you want to transfer resources and you know you no longer need the current value of the original object.
An Example with a Vector
With large objects, moving can save a lot of work. Copying a std::vector involves duplicating all its elements; moving it usually involves transferring the internal buffer.
#include <iostream>
#include <utility>
#include <vector>
int main() {
std::vector<int> datos = {1, 2, 3, 4, 5};
std::vector<int> copia = datos; // copies the elements
std::vector<int> movido = std::move(datos); // transfers the resource
std::cout << "movido: " << movido.size() << "\n";
}The difference is not noticeable with five integers, of course. But with a large buffer, images, huge strings, or system resources, avoiding unnecessary copies does matter.