conceptModern C++~4 min readUpdated 2026-07-02#cpp#references#const#overloads#value-categories

References, const, and overloading

A C++ reference is an alias to an existing object or function, not an owning handle and not a nullable pointer by default. const on a reference means "this access path cannot modify the referent," not "the object is frozen forever." Overloading then uses those types, cv-qualifiers, and value categories to choose a function before runtime. This trio is why modern C++ APIs can say "borrow mutably," "borrow read-only," or "consume this temporary" without inventing a naming convention for every call.

The reset: references are about binding, const is about what this expression may do, and overload resolution is compile-time dispatch over those facts.

How it really works

An lvalue reference T& must bind to an object you can modify through that reference. A const T& can bind to const objects, non-const objects, and temporaries, but mutation through that reference is not allowed. An rvalue reference T&& binds to expiring objects and is the mechanism behind move semantics. The reference itself is not reseated after binding; if you need optional or reseatable access, use a pointer or a wrapper such as std::optional<std::reference_wrapper<T>>.

Form Meaning at the API boundary Usual use
T& required mutable borrow fill, mutate, update in place
const T& required read-only borrow inspect without copying
T* optional borrow or C boundary nullable access, no ownership
T&& expiring object may be consumed move, sink, builder APIs
const T* / const T& cannot modify through this path read-only view of an object

const composes with pointers and members in ways that matter. const int *p means p points to an int you cannot modify through p. int *const p means p itself cannot be reseated. A const member function promises not to modify the observable state of *this through that member function. It can still read, call other const members, and mutate fields marked mutable, which is why const is a type-system promise about an access path, not a deep immutability proof.

Overload resolution starts with a set of candidate functions, filters for viable ones, and ranks conversions. A mutable lvalue prefers T&. A const lvalue prefers const T&. A temporary can bind to const T& or T&&, and T&& is the better match when present. Member functions can also have cv-qualifiers and ref-qualifiers:

void append(std::string_view line) &;
void append(std::string_view line) && = delete;

That says append may be called on an lvalue object, but not on a temporary. This is ordinary overload resolution with the implicit object parameter included in the decision.

Executable artifact: overload selection is visible

The runnable demo lives in examples/modern-cpp/references-const-and-overloading/. It mutates through Packet&, inspects through const Packet&, and selects different overloads for mutable lvalues, const lvalues, and temporaries.

cd examples/modern-cpp/references-const-and-overloading
./run.sh

The central overload set is small enough to read as a table:

static void describe(Packet &) {
    std::cout << "overload: mutable lvalue\n";
}

static void describe(const Packet &) {
    std::cout << "overload: const lvalue\n";
}

static void describe(Packet &&) {
    std::cout << "overload: rvalue, can be consumed\n";
}

The calls choose without runtime branching:

Packet packet{"frame", 64};
const Packet frozen{"frozen", 128};

describe(packet);
describe(frozen);
describe(Packet{"temporary", 8});

The demo also uses a ref-qualified member:

void append(std::string_view line) & {
    lines_.push_back(std::string(line));
}

void append(std::string_view) && = delete;

That prevents accidental mutation of a short-lived Log{} temporary. The type communicates "this operation needs a stable object," and the compiler enforces it at the call site.

Failure modes & trade-offs

  • Dangling references are still UB. Returning T& to a local, storing a reference to a temporary beyond its lifetime, or keeping a reference into a reallocated container is as broken as keeping a dangling pointer.
  • const is not ownership and not deep immutability. Another alias can mutate the same object, and mutable fields can change inside const member functions.
  • Overloads can become ambiguous. Too many overloads, implicit conversions, and default arguments can make a call hard for humans or the compiler to choose.
  • const_cast is not a back door to safety. Removing const and modifying an actually const object is undefined behavior.
  • Reference data members are sticky. They must be initialized, cannot be reseated, and can make assignment operators deleted or surprising. Prefer pointers for optional relationship fields.
  • Rvalue-reference parameters are lvalues when named. Inside void f(T&& x), the expression x is an lvalue. Use std::move(x) only when you intentionally consume it.

In practice

  • Use references for required borrows. T& and const T& say the caller must provide a real object and ownership stays outside.
  • Use pointers when null matters. A nullable parameter should look nullable. Do not use a reference plus out-of-band state.
  • Prefer const early. Mark inspector functions and read-only parameters const; it expands what can call them and documents non-mutation.
  • Keep overload sets semantic. Overloads should represent the same operation over different types or value categories, not unrelated commands with one name.
  • Use ref-qualifiers for fluent APIs. They can prevent calls that would mutate or return references from temporaries.

Connects to: Move semantics and value categories · Smart pointers: unique_ptr, shared_ptr, ownership · Const correctness and what const really promises · What a pointer really is · The C type system is weak · Integer promotions and implicit conversions

Sources

  • cppreference - Reference declaration - lvalue references, rvalue references, reference collapsing, and dangling-reference notes. https://en.cppreference.com/w/cpp/language/reference
  • cppreference - cv qualifiers - const and volatile qualification, const objects, and cv-qualified member functions. https://en.cppreference.com/w/cpp/language/cv
  • cppreference - Overload resolution - candidate selection, viability, conversion ranking, and best viable function rules. https://en.cppreference.com/w/cpp/language/overload_resolution
  • C++ working draft - [dcl.ref] - standard wording for reference declarators and reference rules. https://eel.is/c++draft/dcl.ref
  • C++ working draft - [over.match] - standard wording for overload resolution and matching. https://eel.is/c++draft/over.match
  • C++ Core Guidelines - practical guidance for passing by reference, const-correctness, and overload clarity. https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines