Referencias, const y overloading
Una referencia C++ es un alias a un objeto o función existente, no un handle owning ni un
puntero nullable por defecto. const sobre una referencia significa "este camino de acceso
no puede modificar el referente", no "el objeto queda congelado para siempre". Después,
overloading usa esos tipos, cv-qualifiers y value categories para elegir una función antes
del runtime. Este trío es la razón por la que las APIs C++ modernas pueden decir "borrow
mutable", "borrow read-only" o "consumí este temporario" sin inventar una convención de
nombres para cada llamada.
El reset: las referencias tratan de binding,
consttrata de qué puede hacer esta expresión, y overload resolution es dispatch en compile time sobre esos hechos.
Cómo funciona de verdad
Una lvalue reference T& debe bindear a un objeto que podés modificar a través de esa
referencia. Una const T& puede bindear a objetos const, objetos no const y temporarios,
pero no permite mutación a través de esa referencia. Una rvalue reference T&& bindea a
objetos expiring y es el mecanismo detrás de move semantics. La referencia en sí no se
reseatea después del binding; si necesitás acceso opcional o reseatable, usá un puntero o
un wrapper como std::optional<std::reference_wrapper<T>>.
| Forma | Qué significa en el borde de API | Uso habitual |
|---|---|---|
T& |
borrow mutable requerido | llenar, mutar, actualizar in place |
const T& |
borrow read-only requerido | inspeccionar sin copiar |
T* |
borrow opcional o borde C | acceso nullable, no ownership |
T&& |
objeto expiring que puede consumirse | move, sink, builder APIs |
const T* / const T& |
no se puede modificar por este camino | vista read-only de un objeto |
const compone con punteros y miembros de formas que importan. const int *p significa
que p apunta a un int que no podés modificar a través de p. int *const p significa
que p en sí no puede reseatearse. Una member function const promete no modificar el
estado observable de *this a través de esa función. Todavía puede leer, llamar otros
members const y mutar fields marcados mutable, por eso const es una promesa del type
system sobre un camino de acceso, no una prueba de inmutabilidad profunda.
Overload resolution empieza con un set de candidate functions, filtra las viables y rankea
conversiones. Un lvalue mutable prefiere T&. Un lvalue const prefiere const T&. Un
temporario puede bindear a const T& o T&&, y T&& es el mejor match cuando existe. Las
member functions también pueden tener cv-qualifiers y ref-qualifiers:
void append(std::string_view line) &;
void append(std::string_view line) && = delete;
Eso dice que append puede llamarse sobre un objeto lvalue, pero no sobre un temporario.
Es overload resolution ordinaria con el parámetro implícito del objeto incluido en la
decisión.
Artefacto ejecutable: overload selection es visible
El demo ejecutable vive en
examples/modern-cpp/references-const-and-overloading/. Muta mediante Packet&,
inspecciona mediante const Packet& y selecciona distintos overloads para lvalues
mutables, lvalues const y temporarios.
cd examples/modern-cpp/references-const-and-overloading
./run.sh
El overload set central es lo bastante chico para leerlo como tabla:
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";
}
Las llamadas eligen sin branching de runtime:
Packet packet{"frame", 64};
const Packet frozen{"frozen", 128};
describe(packet);
describe(frozen);
describe(Packet{"temporary", 8});
El demo también usa un member ref-qualified:
void append(std::string_view line) & {
lines_.push_back(std::string(line));
}
void append(std::string_view) && = delete;
Eso previene mutación accidental de un temporario Log{} de vida corta. El tipo comunica
"esta operación necesita un objeto estable", y el compilador lo hace cumplir en el call
site.
Fallas típicas y trade-offs
- Las referencias colgantes siguen siendo UB. Devolver
T&a un local, guardar una referencia a un temporario más allá de su lifetime o mantener una referencia dentro de un container realocado está tan roto como guardar un puntero colgante. constno es ownership ni inmutabilidad profunda. Otro alias puede mutar el mismo objeto, y los fieldsmutablepueden cambiar dentro de member functionsconst.- Los overloads pueden volverse ambiguos. Demasiados overloads, conversiones implícitas y default arguments pueden hacer que una llamada sea difícil de elegir para humanos o para el compilador.
const_castno es una puerta trasera segura. Remover const y modificar un objeto que realmente es const es undefined behavior.- Los reference data members son rígidos. Deben inicializarse, no pueden reseatearse y pueden hacer que los assignment operators queden deleted o sean sorprendentes. Preferí punteros para relaciones opcionales en fields.
- Los parámetros rvalue-reference son lvalues cuando tienen nombre. Dentro de
void f(T&& x), la expresiónxes un lvalue. Usástd::move(x)solo cuando consumís intencionalmente.
En la práctica
- Usá referencias para borrows requeridos.
T&yconst T&dicen que el caller debe proveer un objeto real y que el ownership queda afuera. - Usá punteros cuando null importa. Un parámetro nullable debería verse nullable. No uses una referencia más estado fuera de banda.
- Preferí
consttemprano. Marcá inspector functions y parámetros read-only comoconst; expande quién puede llamarlos y documenta no-mutación. - Mantené semánticos los overload sets. Los overloads deberían representar la misma operación sobre distintos tipos o value categories, no comandos no relacionados con un mismo nombre.
- Usá ref-qualifiers para APIs fluent. Pueden impedir llamadas que mutarían o devolverían referencias desde temporarios.
Conecta con: Move semantics y value categories · Smart pointers: unique_ptr, shared_ptr, ownership · Const correctness y qué promete realmente const · Qué es realmente un puntero · El sistema de tipos de C es débil · Integer promotions y conversiones implícitas
Fuentes
- cppreference - Reference declaration - lvalue references, rvalue references, reference collapsing y notas sobre dangling references. https://en.cppreference.com/w/cpp/language/reference
- cppreference - cv qualifiers - qualification
constyvolatile, objetos const y member functions cv-qualified. https://en.cppreference.com/w/cpp/language/cv - cppreference - Overload resolution - selección de candidates, viabilidad, ranking de conversiones y reglas de best viable function. https://en.cppreference.com/w/cpp/language/overload_resolution
- C++ working draft -
[dcl.ref]- wording estándar para reference declarators y reglas de referencias. https://eel.is/c++draft/dcl.ref - C++ working draft -
[over.match]- wording estándar para overload resolution y matching. https://eel.is/c++draft/over.match - C++ Core Guidelines - guía práctica para pasar por referencia, const-correctness y claridad de overloads. https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines