A variable names an object. A reference provides another name for an existing object; a pointer holds an address and can be empty. Neither automatically keeps that object alive. Understanding the object's lifetime, the interval during which it exists, is essential before using references to avoid copies.
C++ does not generally verify that every borrowed reference remains valid. An expression can compile and still access an object whose lifetime has ended. Start every borrowing decision with two questions: who owns the object, and can that owner destroy or relocate it before the last use?
Choose a parameter contract
| Parameter | Meaning at the call boundary |
|---|---|
int value | Copy a small input |
const std::string& text | Borrow an existing string for reading |
int& value | Borrow an existing integer for modification |
const int* value | Borrow an integer that may be absent |
std::span<int> values | Borrow a contiguous sequence with a size |
A reference must be initialized and cannot later be rebound to another object. Assigning through it changes the referred-to object. A pointer can be reassigned and compared with nullptr. Dereferencing a null pointer is invalid; a non-null pointer can still dangle, so a null check is only one part of the contract.
const controls permitted operations through a particular access path. A const reference prevents modification through that reference, but another non-const alias might still change the underlying object. Similarly, a const span object does not make its elements const; use std::span<const int> for a read-only element view.
Borrow without allocating
Save as references.cpp:
#include <array>
#include <iostream>
#include <span>
#include <string>
void describe(const std::string& name) {
std::cout << "Team: " << name << '\n';
}
void reset(int& value) {
value = 0;
}
void replace_negative(std::span<int> values) {
for (int& value : values) {
if (value < 0) {
reset(value);
}
}
}
void print_optional(const int* value) {
if (value != nullptr) {
std::cout << *value << '\n';
} else {
std::cout << "No score\n";
}
}
int main() {
const std::string team{"Orion"};
std::array<int, 3> scores{4, -2, 7};
describe(team);
replace_negative(std::span<int>{scores});
print_optional(&scores[1]);
print_optional(nullptr);
}
clang++ -std=c++20 -Wall -Wextra -Wpedantic -g references.cpp -o references
./references
Expected output:
Team: Orion
0
No score
&scores[1] obtains an address; *value accesses the pointed-to integer. These operators have different meanings in declarations: int& declares a reference and int* declares a pointer. A span stores a view of the sequence, not a copy of its elements. Passing that view by value is inexpensive and still lets the function modify the original elements.
The example is safe because the array remains alive, its storage does not move and all indexes are valid. C++20 span indexing does not provide a general runtime bounds check. A stored size helps you write valid loops; it does not automatically make every possible access safe.
Lifetime traps
Never return a reference or pointer to an ordinary local variable. The variable is destroyed when its function exits. Returning a value is usually the correct design. Likewise, returning a span into a local array produces a dangling view.
A temporary can bind to a const reference parameter for the duration of the call. Saving that reference elsewhere does not extend the temporary's lifetime beyond the full expression containing the call. Some direct local reference initializations extend a temporary's lifetime, but this is a specific language rule, not a promise that every borrowed expression stays alive.
Container mutations can also invalidate pointers and references. A vector may relocate its elements when it grows. A string view into a string can become invalid after the string is destroyed or its storage changes. A valid view therefore needs both a living owner and a mutation policy that preserves the viewed storage.
Practice and solution guidance
- Add a function that prints a span without modifying elements. Guidance: accept
std::span<const int>and pass the array; no element copy or ownership transfer is needed. - Explain whether
const int*andint* constmean the same thing. Guidance: the first protects the pointed-to value through that pointer; the second prevents reseating the pointer itself. - Design a function that produces three calculated scores. Guidance: return an owning
std::array<int, 3>by value instead of a view into local storage. The caller can create a view afterward.
Primary references
- WG21 C++20 draft: reference declarations.
- WG21 C++20 draft: object lifetimes.
- WG21 C++20 draft: span requirements.
Next: Strings and Vectors in C++.

