Skip to content
C++
Language
since C++98
Basic

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++98

A 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

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

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

cpp
#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)

cpp
#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)

cpp
#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 rvalue

std::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.

cpp
#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

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

cpp
#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 == 1

Best 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:

cpp
const int& danger(const int& x) { return x; }  // x may be a temporary
const int& r = danger(42);  // r is dangling immediately after the call

Forgetting std::move when calling a move constructor explicitly. A named rvalue-reference variable is itself an lvalue:

cpp
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 where
  • reference/language/const-correctness β€” how const interacts with reference parameters and member functions
  • reference/language/auto β€” auto&& deduction and forwarding references in generic lambdas (C++14) and range-for