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

  1. Write a function swap_values(int&, int&) and call it on two variables. What happens if you call it with the same variable twice?
  2. Change bump to take a const int& and compile. Why does the assignment inside the function stop the build?
  3. 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.

  1. 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.

  2. What does sizeof of a reference give?

    Answer

    The size of the referenced object

    The name of a reference is the object for every operation, including sizeof.

  3. 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.

  4. 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 = value changes the referenced object.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. 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.

  10. 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