References
A reference is a second name for an object that already exists. It is not a pointer and it is not a copy: after int& alias = count; the names alias and count refer to the same int, and a write through either one is visible through the other.
Lesson 010 showed that a parameter passed by value is a copy. This lesson replaces that copy with a reference where the function has to change the caller's object, and adds the const reference that avoids copying without allowing changes.
A reference is another name
The declaration int& alias = count; binds alias to count and must be initialised: there is no such thing as a reference that refers to nothing. After the binding, alias and count are two names for one int, and the compiler generates the same access for both.
A write through the reference is a write to the object. The program below assigns 42 to alias and prints count, which is the proof that no copy was involved. sizeof(alias) is sizeof(int), not the size of a pointer.
A reference cannot be reseated later. An assignment to a reference assigns to the object it names, which is the source of the surprise in the last section of this lesson.
#include <iostream>
int main()
{
int count = 10;
int& alias = count;
alias = 42;
std::cout << "count: " << count << '\n';
std::cout << "alias: " << alias << '\n';
std::cout << "sizeof(alias): " << sizeof(alias) << '\n';
}
g++ -std=c++23 -Wall -Wextra alias.cpp -o alias
./alias
count: 42
alias: 42
sizeof(alias): 4
Reference parameters
A parameter declared as int& is bound to the argument, not copied from it. The function can change the caller's object, and the call site shows no & or *: bump(count) reads like a call that might modify its argument.
That is exactly why the course uses references for output parameters only when the function is named for the change, as bump is. A parameter that is read but not written should be a const reference, and one that is written should be obvious from the function name or the return value.
The same function written with a value parameter printed 42 inside and 41 outside in lesson 010. Here the caller's variable is the one that changes.
#include <iostream>
void bump(int& value)
{
value = value + 1;
}
int main()
{
int count = 41;
bump(count);
std::cout << "after bump: " << count << '\n';
}
g++ -std=c++23 -Wall -Wextra bump.cpp -o bump
./bump
after bump: 42
A const reference takes a temporary
A const reference parameter accepts anything the type can be built from, including a literal or a temporary. The compiler creates the temporary, binds the reference to it, and keeps it alive until the end of the full expression.
That is what makes a function with a const std::string& parameter callable with "a literal": no std::string is copied at the call site, and none has to exist before the call. std::string itself is the subject of lesson 017; here only the parameter type matters.
The program prints the text and its size for a named string and for a literal. Both calls reach the same function, and only the first one has a std::string object of its own.
#include <iostream>
#include <string>
void print(const std::string& text)
{
std::cout << '[' << text << "] " << text.size() << '\n';
}
int main()
{
std::string name = "C++";
print(name);
print("a literal");
}
g++ -std=c++23 -Wall -Wextra const_ref.cpp -o const_ref
./const_ref
[C++] 3
[a literal] 9
A reference to a dead local
A reference must outlive the object it names. Returning a reference to a local variable breaks that rule: the local is destroyed when the function returns, and the caller holds a reference to memory that no longer holds an int.
GCC 15 refuses this program outright when the reference is non-const: the local is not available for binding any more, and the error says so. With a const int& return type the same function only earns -Wreturn-local-addr, a warning, and then it is the caller's read that is undefined behaviour. This program is not run.
The fix is to return by value, to return a reference to something the caller owns, or to return a reference to a static object when one shared instance is really what you mean. The next lesson adds const to this decision.
#include <iostream>
int& worst()
{
int local = 7;
return local;
}
int main()
{
std::cout << worst() << '\n';
}
g++ -std=c++23 -Wall -Wextra dangling.cpp -o dangling
dangling.cpp: In function 'int& worst()':
dangling.cpp:6:12: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
6 | return local;
| ^~~~~
A reference cannot be reseated
After int& r = a; the reference is tied to a for the rest of its life. Writing r = b; assigns the value of b into a; it does not make r name b. The output shows both variables holding 2.
This is the one place where a reference behaves differently from a pointer, and it is the reason a reference is the better choice when the binding never changes. A pointer can be reassigned and can be null; a reference can do neither.
When a function needs to point at different objects over time, or at nothing at all, it takes a pointer instead. The course covers raw pointers much later, in part 8, because references cover almost every case before that.
#include <iostream>
int main()
{
int a = 1;
int b = 2;
int& r = a;
r = b;
std::cout << "a: " << a << " b: " << b << '\n';
}
g++ -std=c++23 -Wall -Wextra norebind.cpp -o norebind
./norebind
a: 2 b: 2
Exercises
- Write a function swap_values(int&, int&) and call it on two variables. What happens if you call it with the same variable twice?
- Change bump to take a const int& and compile. Why does the assignment inside the function stop the build?
- Return a reference to a static counter from a function, call it twice and print the result. Why is this one safe where worst() was not?
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What is a reference?
Answer
Another name for an object that already exists
The reference is bound to an object and refers to the same memory, with no separate object of its own.
-
What does
sizeofof a reference give?Answer
The size of the referenced object
The name of a reference is the object for every operation, including sizeof.
-
What does a const reference to a temporary do?
Answer
It extends the lifetime of the temporary to the reference
The special rule for const references is what makes
const std::string& s = make();safe. -
Can a reference be made to refer to another object later?
Answer
No, an assignment writes through it to the original object
There is no reseating operation; after binding,
alias = valuechanges the referenced object. -
What does this program print?
#include <iostream> int main() { int count = 42; int& alias = count; std::cout << "count: " << count << ", alias: " << alias << ", sizeof(alias): " << sizeof(alias) << '\n'; }Answer
count: 42, alias: 42, sizeof(alias): 4
Both names refer to the same int, and sizeof of a reference is the size of the int it names.
-
What does this program print?
#include <iostream> void bump(int& value) { ++value; } int main() { int number = 41; bump(number); std::cout << "after bump: " << number; }Answer
after bump: 42
The reference parameter is bound to the caller's variable, so the increment is visible in main.
-
What does this program print?
#include <iostream> #include <string> void show(const std::string& text) { std::cout << '[' << text << "] " << text.size() << '\n'; } int main() { show("C++"); show("a literal"); }Answer
[C++] 3 / [a literal] 9
The function reads the text and prints its length, and a temporary string is created for the literal.
-
What does this program print?
#include <iostream> int main() { int a = 1; int& b = a; b = 2; std::cout << "a: " << a << " b: " << b; }Answer
a: 2 b: 2
The assignment writes through the reference to b, and a is left at one.
-
What does the compiler say about returning a reference to a local?
Answer
cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
The local is treated as an rvalue on return, and the non-const reference cannot bind to it.
-
Why does
int& r = 5;fail to compile?Answer
A non-const reference cannot bind to a temporary
The rule protects the program from a reference to an object that would die immediately; a const reference is allowed.
The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.
Article author: Arthur Isaev