Exercises — References — lvalue references, difference from pointers
Before we start, one shared picture to anchor the whole page: an lvalue reference is a second label glued to one box, while a pointer is its own box holding an address written on a sticky note.

Keep that image in mind — most wrong answers below come from forgetting that a reference is not a separate box.
Level 1 — Recognition
Here you only need to recognise whether code is legal and what the rules say. No tracing yet.
L1.1 — Legal or not?
For each line, say whether it compiles. Assume int x = 10; already exists above each.
(a) int& r = x;
(b) int& r;
(c) int& r = 5;
(d) const int& r = 5;
(e) int& r = x; r = 20;Recall Solution L1.1
- (a) Legal.
rbecomes another name forx(initialized at declaration ✔). - (b) Illegal. A reference must be initialized at declaration — there is no "empty alias".
- (c) Illegal.
5is a temporary (an rvalue) — it has no address to alias. A non-constlvalue reference cannot bind a temporary. - (d) Legal. A
constreference may bind a temporary and extends its lifetime tor's scope. - (e) Legal. After binding,
r = 20is not a rebind — it writes20intox's box through the alias.xbecomes20.
L1.2 — Match the property
Which of {reference int&, pointer int*} can: (a) be null, (b) be reseated, (c) live uninitialized, (d) do arithmetic, (e) auto-dereference when used?
Recall Solution L1.2
- (a) null → pointer only (
nullptr). - (b) reseated → pointer only.
- (c) uninitialized → pointer only (
int* p;is legal though dangerous). - (d) arithmetic → pointer only (
p + 1). - (e) auto-dereference on use → reference only (
racts like the object; a pointer needs*p).
Level 2 — Application
Now trace the code and produce the printed output.
L2.1 — What prints?
int x = 5;
int& r = x;
r = 99;
std::cout << x << " " << r << " " << (&r == &x);Recall Solution L2.1
int& r = x;gluesrtox's box.r = 99;writes99into that one box.- So
xreads99,rreads99. &r == &xis true, printed as1(there is no second address).
Output: 99 99 1
L2.2 — Two-step aliasing
int a = 3, b = 7;
int& r = a;
r = b; // careful: is this a rebind?
b = 100;
std::cout << a << " " << b << " " << r;Recall Solution L2.2
int& r = a;→raliasesa.r = b;is not a rebind (references can never rebind). It readsb's value (7) and writes it intoa's box. Nowa == 7, andrstill aliasesa.b = 100;changes onlyb's box —randaare untouched.- So
a == 7,b == 100,r == 7.
Output: 7 100 7
L2.3 — swap via reference
Given swap(int& a, int& b){ int t=a; a=b; b=t; } and int x=1, y=2; swap(x, y);, what are x and y?
Recall Solution L2.3
Inside swap, a aliases x, b aliases y. t = a → t = 1. a = b → x = 2. b = t → y = 1.
Result: x == 2, y == 1. See Pass-by-value vs Pass-by-reference.
Level 3 — Analysis
Here you explain why or find the bug.
L3.1 — Spot the dangling reference
int& make() {
int v = 42;
return v;
}
int main() {
int& r = make();
std::cout << r; // what happens?
}Is this safe? Explain in terms of the box picture.
Recall Solution L3.1
Unsafe — undefined behaviour (dangling reference).
vis a local box that lives only whilemake()runs.- Returning
vby reference hands back a label to a box that is destroyed the instantmake()returns. ris now an alias to a dead box; readingrreads freed stack memory. Fix: only return references to objects that outlive the call — members, parameters passed in, orstatics. See Dangling references and lifetime.

L3.2 — Why does one line compile and the other not?
std::string& a = std::string("hi"); // (a)
const std::string& b = std::string("hi"); // (b)Recall Solution L3.2
std::string("hi")is a temporary (an rvalue) — it has no persistent named storage.- (a) A non-
constlvalue reference cannot bind a temporary (you might modify something about to vanish, and there's no stable box to alias). Illegal. - (b) A
constreference may bind a temporary and extends its lifetime tob's scope, so the string stays alive as long asbdoes. Legal. See const correctness.
L3.3 — sizeof reasoning
For int x = 10; int& r = x; int* p = &x; on a 64-bit machine, predict sizeof(x), sizeof(r), sizeof(p).
Recall Solution L3.3
sizeof(x) == 4(a typicalint).sizeof(r) == 4—sizeofon a reference reports the size of the referred-to object, not of any hidden pointer. A reference is not a separate object.sizeof(p) == 8— a pointer is its own box holding a 64-bit address. See sizeof and object identity.
Level 4 — Synthesis
Combine several rules to produce or fix code.
L4.1 — Rewrite pointer-swap as reference-swap
Given the C-style version below, rewrite it using references and give the new call site.
void swap(int* a, int* b){ int t=*a; *a=*b; *b=t; }
swap(&x, &y);Recall Solution L4.1
void swap(int& a, int& b){ int t = a; a = b; b = t; }
swap(x, y); // no & at the call, no * insideThe references alias x and y directly, so the ugly &/* bookkeeping disappears while the effect is identical (x and y swap).
L4.2 — Design an efficient read-only printer
Write the signature of a function that prints a std::string without copying it and without permitting modification, and explain each keyword. Then show it accepts a literal.
Recall Solution L4.2
void print(const std::string& s);
print("hello"); // legal&→ pass by reference, so no copy of a possibly-large string is made.const→ promise not to modify the caller's string; also allows binding a temporary."hello"is a temporarystd::string; aconstreference may bind it (its lifetime is extended for the call). A non-conststd::string&would reject the literal.
L4.3 — Fix the dangling return
Rewrite int& make() from L3.1 so it is safe, keeping the return-by-reference style. State when your fix is valid.
Recall Solution L4.3
One valid fix — reference a static (outlives the call):
int& make() {
static int v = 42; // lives for the whole program
return v; // safe: v never dies
}Another valid pattern is to reference a parameter passed in by reference:
int& choose(int& lo, int& hi, bool pickHi){ return pickHi ? hi : lo; }The rule: only return a reference to something that outlives the call — a member, a passed-in reference, or a static. Returning a reference to a plain local is always wrong.
Level 5 — Mastery
Deep integration — subtle behaviour and full reasoning.
L5.1 — Alias-of-alias
int x = 1;
int& r = x;
int& s = r; // what does s alias?
s = 8;
std::cout << x << " " << r << " " << s
<< " " << (&s == &x);Recall Solution L5.1
int& s = r;bindssto whatrrefers to, i.e. tox's box (references are transparent — there is no "reference to a reference" object). Sos,r,xare three labels on one box.s = 8;writes8into that box.- All three read
8;&s == &xis true →1.
Output: 8 8 8 1
L5.2 — const-ref temporary lifetime, counted
int f() { return 7; }
int main() {
const int& r = f(); // binds the returned temporary
int y = r + 3;
std::cout << r << " " << y;
}Is r valid on the second line? What prints?
Recall Solution L5.2
f()returns the value7as a temporary.const int& r = f();binds that temporary and extends its lifetime to the end ofr's scope (the wholemain). Soris perfectly valid afterward.y = r + 3 = 7 + 3 = 10.
Output: 7 10
(Contrast L3.1: there the temporary was a local named object returned by reference, whose lifetime is not extended — that dangles. Lifetime extension only applies to a temporary bound directly to a const reference.)
L5.3 — Reference member and identity
struct Wrap { int& ref; };
int a = 5, b = 9;
Wrap w{a};
w.ref = b; // rebind or write?
std::cout << a << " " << b;Recall Solution L5.3
Wrap w{a};binds the memberreftoaat construction — a reference member must be initialized in the initializer list / aggregate init, and can never be rebound afterward.w.ref = b;is therefore not a rebind. It readsb(9) and writes it intoa's box. Nowa == 9.bis untouched →b == 9.
Output: 9 9
(Because ref cannot be rebound, a class with a reference member has no default copy-assignment — a subtle real-world consequence.)
L5.4 — Full contrast summary
In one sentence each, state the semantic difference for: (a) &-of, (b) nullability, (c) rebinding, (d) sizeof, between int& and int* const.
Recall Solution L5.4
- (a)
&-of: for a reference,&r == &x(original's address); forint* const p,&pis the pointer's own address. - (b) nullability: a reference can never be null; a
constpointer can holdnullptr(theconstonly fixes which address, not that it's valid). - (c) rebinding: neither can be reseated — this is the one property they share.
- (d) sizeof:
sizeof(reference) == sizeof(referent);sizeof(int* const) == 8(its own box). See Rvalue references and move semantics for howT&&extends this family.
Active recall
Recall Rapid fire (cover answers)
Does r = b; after int& r = a; rebind r? ::: No — it writes b's value into a's box.
sizeof(r) for int& r = x;? ::: Same as sizeof(x) (4), not a pointer size.
Can int& bind std::string("hi")? ::: No; only const std::string& can (and extends its lifetime).
Return-by-reference to a static local — safe? ::: Yes, the static outlives the call.
int& s = r; where r is a reference — what does s alias? ::: The same object r refers to (references are transparent).
Connections
- Parent: References overview
- Pointers in C++ — the reseatable, nullable cousin
- Pass-by-value vs Pass-by-reference
- const correctness
- Rvalue references and move semantics
- Dangling references and lifetime
- sizeof and object identity