Question bank — References — lvalue references, difference from pointers
First: the memory model these traps attack
Before any trap makes sense you need the picture in your head. Everything below is just this one image, poked from different angles.
Look at the figure. The reference r draws no new box — it is a tag hanging off x's box, which is why &r reads 0x100 (x's address) and sizeof(r) measures the int, not an address: there is no second box to measure. The pointer p is a box at 0x200; &p is 0x200, sizeof(p) is the size of an address, and *p follows the stored 0x100 back to x. Keep this side-by-side in mind for every "why" on this page. It is the crisp version of what sizeof and object identity formalizes.
In the top row, p = &y; erases 0x100 from p's box and writes 0x200 — p now points at y. In the bottom row r = y; looks identical but there is no address inside r to overwrite; the line copies y's value into x's box. That single distinction powers half the "Spot the error" items below.
The figure shows the two diagnostics side by side: the reference error is caught by the compiler (a red stop before the program ever runs), while the uninitialized-pointer error slips through compilation and only explodes at runtime. This is why references push a whole class of bugs from "runtime undefined behavior" back to "won't compile".
Read the red bar: without the const-reference binding a temporary dies at the end of its full expression (the short bar); the binding stretches it to the end of r's scope (the long bar). This is the motivating reason const T& parameters can accept literals and function results, and it ties directly into const correctness (the const is what makes the extension safe — you promise not to write) and into Dangling references and lifetime (use r after the bar ends and it dangles).
True or false — justify
Every claim below is a sentence people commonly believe. Decide true/false, then give the why before revealing. Keep the two memory figures above in view.
A reference is a special kind of pointer that C++ hides from you.
0x200); a reference is not a distinct object at all, just a second name label on the existing box, so there is nothing to "hide".int& r = x; copies the value of x into r.
= binds the alias; no new box, no copy. Only a later r = ... writes into the shared box.After int& r = x;, the expression &r gives the address of the reference itself.
&r gives &x (0x100 in s01), because the reference has no box of its own and therefore no separate address to hand out.A const int& r is exactly the same thing as an int* const p.
p is still a real box (s01) holding an address you can inspect and dereference; r is not a box, has no address of its own, and is auto-dereferenced.You can create a reference now and decide what it aliases later.
Two different references can be made to alias the same variable.
int x; int& a = x; int& b = x; hangs three name labels off one box. Aliasing one target from many names is fine; what's forbidden is rebinding one name.Once bound, r = y; makes r start aliasing y instead of x.
y into x (the box r names). A reference can never be reseated onto a different box.sizeof(r) for int& r = x; returns the size of a machine address (like a pointer).
sizeof reports the size of the referred-to type (sizeof(int)), not an address size.A const int& parameter is slower than passing int by value.
int, by-value is typically as fast or faster; but for large objects const T& avoids a copy, which is why it exists. See Pass-by-value vs Pass-by-reference.const int& r = 5; is illegal because 5 has no address.
5 and extends its lifetime (the long red bar in s04). Only the non-const int& r = 5; is illegal.Spot the error
Each snippet has a bug or a misconception. Name it and say why.
int& r; r = x;
int& r; is a compile-time error (s03) because there is no box to bind later. You cannot declare an empty alias and fill it on the next line.int& r = 5;
5 has no persistent box to alias. Use const int& r = 5;, whose const promise makes lifetime extension safe (s04).int& f() { int x = 5; return x; }
f returns — a dangling reference and undefined behavior. See Dangling references and lifetime.void inc(int a) { a++; } ... int n = 0; inc(n); and expecting n == 1.
a is passed by value, a copy in its own box; a++ changes the copy only. To modify the caller use int& a so a is a label on the caller's box.int x = 10; int& r = x; int& r = y; (in the same scope, trying to "reseat").
r in the same scope is an error; and even conceptually you can't reseat a reference (s02 bottom row) — it stays bound to x for life.void print(std::string& s); print("hi");
std::string& can't bind it. Change the parameter to const std::string&, which may bind temporaries and extend their lifetime for the call.int* p = nullptr; int& r = *p;
std::string& s = getName(); where getName() returns std::string by value.
const std::string& s = getName(); (which extends the temporary's life, s04) or just take by value.Why questions
Why must an lvalue reference be initialized, when a pointer need not be?
nullptr or garbage until you assign it.Why can't int& r = 5; compile but const int& r = 5; can?
const promises no writes, so binding the temporary and extending its life (s04) is safe. Ties to const correctness.Why does &r return the original's address rather than the reference's own?
r and x denote the exact same storage, so their addresses are identical. See sizeof and object identity.Why does sizeof(r) yield sizeof(int) and not the size of an address?
sizeof measures an object, and a reference is not one — there is no address-holding box behind r (unlike the pointer box in s01). So the language defines sizeof on a reference to measure the type it aliases, i.e. the int itself.Why prefer a reference over a pointer when a function must always receive a valid object?
Why prefer a pointer over a reference for an optional argument?
nullptr to mean "no object here"; a reference has no box and cannot express absence, so an optional parameter is naturally a pointer (or std::optional). Compare with Pointers in C++.Why does passing a big object as const std::string& matter but passing int that way rarely does?
const& avoids copying the whole string's bytes; for an int, a copy is one machine word — cheaper than the indirection a reference introduces.Why is "a reference is just a const pointer" a seductive but wrong analogy?
const pointer is still an inspectable, dereferenceable box with its own address, while a reference has no separate identity at all (s01).Why does swap(int& a, int& b) need no * inside its body while the pointer version does?
a and b are already name labels on the caller's boxes, so plain a, b read/write them directly. Pointers hold addresses in their own boxes, so you must dereference with * to reach the value.Edge cases
Can you have a reference to a reference, like int& & rr = r;?
Can you make an array of references, int& arr[3];?
Can a reference alias itself, e.g. int& r = r;?
r isn't initialized yet at that point, so you're binding to an uninitialized/undefined thing; it's undefined behavior, not a valid alias.Can you take the address of a reference to store it in a pointer, int* q = &r;?
q ends up holding &x (s01), not some private reference address — there isn't one. q points at the aliased object, exactly as if you'd written &x.Can you bind a reference to a bit-field, e.g. struct S { int b : 3; }; int& r = s.b;?
const int& r = s.b; is allowed, binding a copy held in a temporary, not the bit-field itself.)Can you bind a reference to a variable declared register, e.g. register int x; int& r = x;?
register object was ill-formed historically (and register is deprecated/removed in modern C++); since a reference conceptually needs the object's address, binding to a genuine register-only object doesn't work — the object must live in addressable memory.What happens to a const int& bound to a temporary once its scope ends?
Is int& r = x; where x is itself a reference (int& x = y;) binding to x or to y?
y — a reference has no box of its own, so binding to x binds to whatever box x already labels, i.e. y. All three names hang off the one box.Can a reference bind to a const object non-const, e.g. const int c = 5; int& r = c;?
r into a const object. You need const int& r = c;, preserving the promise not to modify. See const correctness.If p is a valid pointer, is int& r = *p; legal and safe?
*p names a live object. The reference now labels that box; if the object is later destroyed, r dangles even though "references can't be null".Connections
- References — lvalue references, difference from pointers — parent topic
- Pointers in C++ — the nullable, reseatable cousin whose separate box (s01) these traps contrast against
- Pass-by-value vs Pass-by-reference — why copies vs aliases matters at call sites
- const correctness — the
constthat makes temporary lifetime extension (s04) safe - Rvalue references and move semantics — where reference collapsing lives
- Dangling references and lifetime — the returning-local and end-of-scope cases (s04)
- sizeof and object identity — why
&r == &xandsizeof(r) == sizeof(int)(s01)