References
A reference is an alias for an existing object; C++ provides lvalue references (C++98) and rvalue references (C++11) with distinct binding and lifetime rules.
Referencesince C++98A reference is an alias β an alternative name bound to an existing object β that cannot be reseated after initialization and that carries no storage of its own.
Overview
C++ has two reference categories introduced at different points in the standard:
Lvalue references (T&) arrived in C++98 and bind to named, addressable objects. Their primary roles are avoiding copies in function parameters and enabling in/out semantics.
Rvalue references (T&&) arrived in C++11 and bind to temporaries or objects explicitly cast to rvalue. They power move semantics and perfect forwarding.
A third surface, forwarding references (also called universal references), appears when T&& is written in a deduced context β either a function template parameter or auto&&. These collapse based on the value category of the argument, which is why they can bind to both lvalues and rvalues.
Syntax
int x = 42;
int& lref = x; // lvalue reference β binds to named object
const int& clref = 99; // const lvalue reference β extends lifetime of temporary (C++98)
int&& rref = 7 * 6; // rvalue reference β binds to temporary (C++11)
auto&& fwd = x; // forwarding reference β deduced as int& (C++11)
auto&& fwd2 = 42; // forwarding reference β deduced as int&& (C++11)Reference collapsing (C++11)
When references are layered through template substitution, the compiler applies collapsing rules. In practice: any combination involving an lvalue reference collapses to an lvalue reference; two rvalue references remain an rvalue reference.
| Written | Result |
|---|---|
T& & | T& |
T& && | T& |
T&& & | T& |
T&& && | T&& |
This is what makes std::forward work correctly.
Examples
Lvalue references as function parameters
Passing by const T& avoids a copy and remains valid for any argument that is implicitly convertible to T.
#include <string>
#include <iostream>
void print(const std::string& s) { // no copy, accepts temporaries
std::cout << s << '\n';
}
std::string& front(std::vector<std::string>& v) { // returns alias into v
return v.front();
}
int main() {
std::vector<std::string> names = {"alice", "bob"};
front(names) = "charlie"; // assigns through the reference
print(names.front()); // prints "charlie"
print("temporary"); // const& extends lifetime of the temporary
}Rvalue references and move semantics (C++11)
#include <vector>
#include <utility>
class Buffer {
std::vector<char> data_;
public:
explicit Buffer(std::size_t n) : data_(n) {}
// Move constructor β steals the resource instead of copying it
Buffer(Buffer&& other) noexcept : data_(std::move(other.data_)) {}
// Move assignment
Buffer& operator=(Buffer&& other) noexcept {
data_ = std::move(other.data_);
return *this;
}
};
Buffer make_buffer(std::size_t n) {
return Buffer{n}; // NRVO applies; if not, move constructor is called
}Perfect forwarding with forwarding references (C++11)
#include <utility>
#include <string>
template <typename T, typename Arg>
T make(Arg&& arg) { // Arg&& is a forwarding reference
return T(std::forward<Arg>(arg)); // preserves lvalue/rvalue category
}
std::string s = "hello";
auto s1 = make<std::string>(s); // copies: s is lvalue
auto s2 = make<std::string>(std::move(s)); // moves: cast to rvaluestd::forward<Arg>(arg) casts arg back to whatever category Arg was deduced as. Without it, named parameters are always lvalues inside the function body.
Lifetime extension via const lvalue reference (C++98)
A const T& bound directly to a temporary extends that temporary's lifetime to match the reference's scope.
#include <string>
const std::string& get() {
return "hello"; // DANGER: dangling β temporary destroyed at semicolon
}
void safe() {
const std::string& r = std::string("hello"); // OK: lifetime extended
// r is valid until the end of this scope
}This rule applies only when the reference is the direct binding site. It does not propagate through function returns.
Reference members in classes
class View {
const std::vector<int>& data_; // reference member β no ownership
public:
explicit View(const std::vector<int>& v) : data_(v) {}
int operator[](std::size_t i) const { return data_[i]; }
};Because reference members cannot be reseated, a class with a reference member is not assignable by default; the compiler deletes the copy-assignment operator.
std::ref and std::cref (C++11)
Standard algorithms and thread constructors copy their arguments. To pass a reference through such an interface, wrap it with std::ref or std::cref from <functional>.
#include <functional>
#include <thread>
void increment(int& n) { ++n; }
int count = 0;
std::thread t(increment, std::ref(count)); // passes reference, not copy
t.join();
// count == 1Best Practices
Prefer const T& for read-only parameters of non-trivial types to avoid copies without sacrificing call-site flexibility. For trivially copyable types smaller than a pointer, pass by value instead.
Use T&& in move constructors and move-assignment operators and mark them noexcept (C++11). The standard library moves elements into containers only when the operation is marked noexcept.
Use forwarding references only in templates. Writing T&& in a non-template context is always an rvalue reference, not a forwarding reference. The two look identical but behave differently.
Never return a local variable by reference. The object is destroyed when the function returns; the caller holds a dangling reference. Return by value and rely on NRVO or move semantics.
Avoid reference members in copyable types. They silently delete copy-assignment and complicate object composition. Consider a pointer or std::reference_wrapper if reseatability matters.
Common Pitfalls
Dangling references from temporaries. Assigning a reference-to-temporary to const T& inside a function parameter does not extend lifetime:
const int& danger(const int& x) { return x; } // x may be a temporary
const int& r = danger(42); // r is dangling immediately after the callForgetting std::move when calling a move constructor explicitly. A named rvalue-reference variable is itself an lvalue:
void process(Widget&& w) {
Widget copy = w; // copies β w is a named variable (lvalue)
Widget moved = std::move(w); // moves β correct
}Mistaking auto&& for "rvalue reference." auto&& is a forwarding reference; its deduced type depends on the initialiser's value category. Use it deliberately (range-for loops, generic lambdas) rather than as a shorthand for auto.
Reference collapsing surprises in templates. If a template parameter is substituted with a reference type and then another && is appended, collapse rules apply silently. This is intentional β it enables std::forward β but can be surprising when inspecting deduced types.
See Also
reference/language/value-categoriesβ lvalue, xvalue, and prvalue classifications that govern which references can bind wherereference/language/const-correctnessβ howconstinteracts with reference parameters and member functionsreference/language/autoβauto&&deduction and forwarding references in generic lambdas (C++14) and range-for