References
2 min read
References
A reference is an alias — another name for an existing variable. Unlike pointers, references cannot be null, cannot be reseated after initialization, and require no special syntax to use.
Lvalue references
An lvalue reference binds to a named object:
#include <iostream>
int main() {
int x = 10;
int& ref = x; // ref is an alias for x
ref = 20;
std::cout << x << "\n"; // 20
std::cout << &x << "\n"; // same address
std::cout << &ref << "\n"; // same address
}
Once bound, a reference cannot refer to a different object. There is no "null reference" — a reference is always valid.
References as function parameters
The most important use of references is in function signatures:
#include <iostream>
#include <string>
// const ref: read-only, no copy — the standard pattern for large inputs
void print_name(const std::string& name) {
std::cout << name << "\n";
}
// non-const ref: modify the caller's variable
void uppercase(std::string& s) {
for (char& c : s) c = toupper(c);
}
int main() {
std::string w = "hello";
print_name(w); // hello
uppercase(w);
print_name(w); // HELLO
}
Returning references
A function can return a reference to let the caller modify a member:
#include <vector>
class Grid {
std::vector<int> data;
int cols;
public:
Grid(int rows, int cols) : data(rows * cols, 0), cols(cols) {}
int& at(int row, int col) {
return data[row * cols + col];
}
};
int main() {
Grid g(3, 4);
g.at(1, 2) = 99; // assign through the returned reference
}
Never return a reference to a local variable — it becomes a dangling reference when the function returns.
Rvalue references (C++11)
An rvalue reference (T&&) binds to a temporary (an rvalue) — a value that is about to be destroyed. They are the mechanism behind move semantics:
#include <string>
#include <iostream>
void process(std::string&& s) {
std::cout << "got temporary: " << s << "\n";
// safe to move from s since it is a temporary
}
int main() {
process("hello"); // string literal is a temporary
process(std::string("world")); // explicit temporary
std::string s = "existing";
process(std::move(s)); // cast s to rvalue — s is now "moved-from"
std::cout << s << "\n"; // s is valid but unspecified (likely empty)
}
std::move does not move anything — it just casts to an rvalue reference, signaling that the caller no longer needs the value. The actual move happens in the move constructor or move assignment operator of the receiving type.
Reference collapsing
When templates produce T&& where T is already a reference, the references collapse:
| Written type | Resulting type |
|---|---|
T& & |
T& |
T& && |
T& |
T&& & |
T& |
T&& && |
T&& |
This rule enables perfect forwarding (std::forward<T>), which preserves the value category of function arguments when passing them along to another function.
Learn modern C++ through working code. Each chapter introduces one concept — variables, functions, classes, templates, smart pointers, concurrency — with a clear example, a line-by-line explanation, and notes on how it applies in real programs.
View book →