C++ Part I · Module 6 · Functions
const References and Cheap, Safe Parameters
Chapter 6.4 gave you a parameter form that avoids copying and lets a function reach the caller's object, and then pointed out that it is the wrong tool for the job most people reach for it with. The common case is not wanting to change a large object — it is wanting to read one without paying to duplicate it. A plain reference does that, and in exchange it tells every caller that their data might be modified, gives the compiler nothing to check, and refuses arguments as ordinary as a literal or the result of a calculation. This chapter adds the word const and all three of those problems disappear together. That is worth more than the convenience, because it means you are not memorising three separate behaviours of a special parameter form: nothing is copied for the same reason as before, modification becomes a compile error because you said it would not happen, and the argument restrictions lift precisely because a function that promises not to write has no need of something writable. By the end you should be able to derive each property from the word rather than recall it — and also to say where this form is the wrong choice, which is a question most treatments skip.
Foundation17 min readC++Part IFundamentalsFunctions
Part I · Module 6 · Chapter 6.5 · const References and Cheap, Safe Parameters
1. One Word
void scale(SampleBlock& block, int factor); // 6.4: may modify
double mean(const SampleBlock& block); // 6.5: may only readconst SampleBlock& is read "reference to const SampleBlock". As a parameter type it means: do not copy this, and I will not change it.
That is two statements in one type, and the chapter is about the fact that the second one buys the first's remaining problems away.
The word is Chapter 3.9's const, applied to the thing a reference refers to rather than to a variable. Nothing about its meaning has changed: const is a promise, checked at compile time, that an object will not be modified through this name.
2. The Three Properties
The form has three properties, and they are usually presented as a list to remember. They are better understood as one word with three consequences:
| Property | Measured in |
|---|---|
| Nothing is copied | §3 |
| Modification is a compile error | §4 |
| It binds to almost any argument | §5 |
§6 is why all three follow from const.
3. Nothing Is Copied
This is inherited unchanged from Chapter 6.4: a reference is another name for an object, so there is no second object. Chapter 6.2 §5 measured it on the 16 KB aggregate:
sizeof(SampleBlock) = 16384 bytes
caller's block at 0x16ddf286c
by value: parameter at 0x16ddee868
by const&: parameter at 0x16ddf286cThe const& parameter is at the caller's own address. Per Chapter 6.2 §3's caveat, compare the numbers within that one run: the by-value parameter is somewhere else, so 16,384 bytes were copied to make it; the const& parameter is the caller's object itself.
And the timing from Chapter 6.2 §6, 200,000 calls at -O2: 62–63 ms by value against 29–30 ms by const&, roughly twice. The same three qualifications apply — the first run of a fresh process was slower, the ratio is specific to this program, and the deterministic claim is the address rather than the milliseconds.
4. Modification Is a Compile Error
static void f(const int& n) { n = 1; }error: cannot assign to variable 'n' with const-qualified type 'const int &'An error, not a warning. Compare that to the protection available elsewhere in this module: Chapter 6.3 §4's missing return and §6's returned reference to a local were both warnings on undefined behaviour. This is the stronger kind of check, and it is available because the promise is in the type.
Two things follow that are easy to undervalue.
It is checked against later edits, not just against you today. A plain SampleBlock& parameter that happens to only read is a fact about the current body. A const SampleBlock& parameter is a fact about every future version of it. Someone adding a write six months from now gets an error rather than a surprise for the caller.
It is documentation that cannot drift. Chapter 6.4 §4 observed that the ampersand in a declaration is the only notice a caller gets that an argument might change. const is the notice that it will not, and unlike a comment it is enforced.
This is Chapter 5.6 §5's finding in a second setting. There, const int& v as a range-for loop variable turned an accidental write into cannot assign to variable 'v' with const-qualified type 'const int &' — the same error, from the same cause, because a loop variable and a parameter are both names being bound to something.
5. It Binds to Almost Anything
This is the property that is not obvious, and the one that makes the form usable everywhere.
Chapter 6.4 §5 measured three refusals for a non-const reference. All three succeed with const:
#include <iostream>
static void reads_only(const int& n)
{
std::cout << " reads_only sees " << n
<< " at address " << static_cast<const void*>(&n) << '\n';
}
int main()
{
int cycles = 7;
const int fixed = 11;
std::cout << "cycles at " << static_cast<const void*>(&cycles)
<< ", fixed at " << static_cast<const void*>(&fixed) << '\n';
reads_only(cycles); // binds to a variable
reads_only(fixed); // binds to a const object
reads_only(42); // binds to a literal -- a temporary is created
return 0;
}Measured output:
cycles at 0x16dab28a8, fixed at 0x16dab28a4
reads_only sees 7 at address 0x16dab28a8
reads_only sees 11 at address 0x16dab28a4
reads_only sees 42 at address 0x16dab28a0Read the addresses against the first line.
The first call shares cycles' address. The caller's variable, by another name.
The second call shares fixed' address. A const object, which Chapter 6.4 §5 refused outright, is accepted — because the parameter is not asking for permission to write.
The third call has an address that is neither. 42 is a literal with nowhere to live, so there was nothing to be another name for. C++ creates an unnamed object holding 42 — a temporary — and binds the reference to that. The address printed is where that temporary was put.
The contrast is total:
| Argument | int& (6.4) | const int& |
|---|---|---|
| a variable | binds | binds |
a const object | refused — would lose const qualifier | binds |
| a literal | refused — expects an lvalue | binds, via a temporary |
| a calculation | refused — expects an lvalue | binds, via a temporary |
The one rule about that temporary. It lives until the end of the full expression containing the call — which is to say, for the whole duration of the call and no longer. So reading it inside the function is entirely safe, and keeping the address or the reference for later is not. That second half is the subject of Chapter 8.8, and Chapter 7.2's distinction between lifetime and scope is what makes it explicable. For a parameter that only reads during the call, the rule you need is the first half.
6. Why All Three Come From One Word
Here is the synthesis, and it is the thing worth carrying out of this chapter.
No copy is a property of the reference, not of const. Chapter 6.4 already had it.
The enforced read-only is const doing exactly what Chapter 3.9 said it does.
The relaxed binding is a consequence of the read-only promise, and this is the part that looks like a separate feature and is not. Chapter 6.4 §5 explained its refusals in one sentence: a function that promises to write needs something writable to write to, and a literal, a const object and a calculation are all un-writable. Remove the promise to write and every one of those objections evaporates. There is nothing left to object to.
So the rule is not "const& binds to more things because the language gives it a special privilege". It is:
A reference that cannot be written through has no requirement that its target be writable. The three properties are one reference and one promise, not three rules.
That is why const& is the most common parameter form in real C++. It costs nothing, it refuses nothing a reader would expect to pass, and it says the true thing about what the function does.
7. When Not to Use It
Most treatments stop at §6. The honest version has this section, because const& is a default rather than a law.
Not for the fundamental types. const int& for an int is worse than int.
A reference is itself about the size of an address. sizeof will not show you that directly — sizeof(int&) reports the size of the referent, 4 — so measure it where it is visible, inside an aggregate:
sizeof(int) = 4
sizeof(int&) [referent] = 4
sizeof(struct{int& r;}) = 8
sizeof(struct{int v;}) = 4Eight bytes against four. So passing an int by const& replaces a 4-byte copy with something twice the size, adds a level of indirection for the compiler to see through, and gives up a usable local scratch variable. Pass an int, a double, a char, a bool by value.
Not when you need your own copy anyway. If the body's first act is to copy the parameter so it can modify it, take it by value and let the call do the copying — one copy either way, and the signature is simpler.
Not when the function must modify the caller's object. That is Chapter 6.4's form, and no amount of preference changes it.
Not when you will store the reference past the call. §5's temporary rule is the reason, and Chapter 8.8 is the chapter. A parameter that only reads during the call is safe; a reference kept beyond it is a question about lifetime.
Two forms that refine this later, named so you know the subject is not closed:
| Form | Where | For |
|---|---|---|
std::string_view | Chapter 9.6 | read-only text, without even requiring a std::string |
| by-value plus move | Chapter 13.8 | when the function genuinely takes ownership |
Neither changes the rule in §6's callout. Both are refinements for cases the rule handles adequately and not optimally.
8. What to Actually Do
Default to const& for anything that is not a fundamental type and that you only read.
Pass fundamental types by value. const int& is not an improvement.
Use & without const only when modification is the job — and name the function after it.
Write const first in the parameter list of your mind. Adding it later is an edit; starting with it is free.
Expect const& to accept literals and calculations. It is not a special case; it is the absence of a requirement.
Do not keep a reference parameter past the call. Chapter 8.8.
Let the compiler check you. If a const& parameter stops compiling because the body now writes to it, that is the form working.
9. Mistakes Worth Making Once
Using & where const& was meant. It works, avoids the copy, and declares an intent you do not have.
Writing const int& for an int. Measured: a reference is about the size of an address, so there is nothing to save and a local scratch value to lose.
Expecting a warning when a read-only parameter is modified. It is an error — cannot assign to variable 'n' with const-qualified type.
Being surprised that const& accepts a literal. Measured: it binds, via a temporary at its own address.
Thinking that binding is a special privilege. It is the absence of a requirement that the target be writable.
Storing a const& parameter for later. The temporary from a literal argument is gone after the call.
Taking const& and then copying it in the body. Take it by value instead.
Passing a 16 KB object by value because the signature looked simpler. Measured: 16,384 bytes and roughly twice the time, per call.
10. Interview Relevance
This is the parameter question that comes up most, and the weak answer is a recitation.
"Why const&?" A strong answer gives all three properties and says the third follows from the second: no copy because it is a reference, no modification because of const, and it binds to literals and temporaries precisely because it promises not to write, so there is no need for a writable target. Candidates who present those as three facts have memorised the idiom; candidates who derive the third have understood it.
"Would you use const int&?" No — pass an int by value. Knowing where the default stops applying is the follow-up that separates an idiom from a judgement.
"What does const& bind to that & does not?" A const object, a literal, and the result of a calculation. If you can give the two clang notes — would lose const qualifier and expects an lvalue — you are describing something you have hit.
11. What You Should Be Able to Say Now
const T&as a parameter means do not copy this, and I will not change it — two statements in one type.- No copy: measured, the
const¶meter was at the caller's own address while the by-value parameter was elsewhere. - Measured cost avoided: 16,384 bytes per call, and 62–63 ms against 29–30 ms over 200,000 calls.
- Modification is an error, not a warning:
cannot assign to variable 'n' with const-qualified type 'const int &'. - That check applies to every future edit of the body, not just to the version you wrote.
- It is the same error Chapter 5.6 §5 measured for a
constrange-for loop variable, from the same cause. - It binds to a variable, a
constobject, a literal and a calculation — measured: three calls, three addresses. - Chapter 6.4's non-
constform refused the last three:would lose const qualifierandexpects an lvalue. - A literal argument creates a temporary, which lives for the duration of the full expression — safe to read during the call, not to keep.
- All three properties come from one word. The relaxed binding is not a privilege; it is the absence of a requirement that the target be writable.
- The rule: fundamental types by value, larger read-only by
const&, modified by&. const int&is worse thanint— a reference is about the size of an address, with indirection added and scratch space lost.- Do not use it when you need your own copy, when you must modify, or when you will keep the reference past the call.
- Chapter 9.6's
std::string_viewand Chapter 13.8's by-value-plus-move are refinements, not replacements.
12. Exercises
Exercise 1 — choose the form. Give the parameter type for each: (a) an int you only read (b) a 16 KB block you only read (c) a 16 KB block you must zero (d) a double used as scratch inside the function (e) two ints to be exchanged.
Exercise 2 — predict the diagnostic. For void f(const int& n) and void g(int& n) with int a = 1; const int c = 2;, say which of these compile and give the diagnostic where they do not: (a) f(a) (b) f(c) (c) f(3) (d) f(a + 1) (e) g(c) (f) g(3).
Exercise 3 — read the addresses. §5's program printed three different addresses for three calls. Say which caller object each of the first two corresponds to, what the third one is, and how long it exists.
Exercise 4 — derive it. Without looking at §6, explain why a const& parameter accepts a literal when a plain & parameter does not. Your answer should not use the word "special".
Exercise 5 — find the dishonest signature. void summarise(SampleBlock& block); reads block and never writes it. Say what is right about it, the two things it costs, and the fix.
Exercise 6 — where the default stops. For each, say whether const& is the right choice and why: (a) const double& reading (b) const SampleBlock& block, read only (c) const SampleBlock& block, copied immediately into a local to be modified (d) const int& index, used to index an array.
Exercise 7 — the error you want. A const SampleBlock& parameter stops compiling after a colleague adds a line that writes to it. Say what the compiler reports, why this is the form succeeding rather than failing, and the two different correct resolutions depending on what the function is now supposed to do.
13. Summary
const T&means no copy, and no modification — one type making two statements.- Measured: the
const¶meter shared the caller's address; by value copied 16,384 bytes and took roughly twice the time. - Modification is a compile error, not a warning, and it holds for every future edit.
- Measured: it binds to a variable, a
constobject and a literal — three calls, three addresses. - Chapter 6.4's plain
&refused theconstobject, the literal and the calculation. - A literal argument makes a temporary that lives for the full expression — read it, do not keep it.
- All three properties follow from
const. The relaxed binding is the absence of a requirement, not a privilege. - The rule: fundamental types by value, larger read-only by
const&, modified by&. const int&is worse thanint.- Not for when you need your own copy, must modify, or will store the reference — Chapter 8.8.
Three parameter forms and one return mechanism is enough to write any function signature in this curriculum, which makes the rest of the module about the conveniences and the hazards built on top of them. The next chapter is a convenience: a way to let a caller omit arguments they do not care about, so one function can serve several call shapes. It is genuinely useful in a narrow band and it has rules that are easy to get wrong — including one about where the default may be written that produces a compile error people find baffling, and one about when the default is evaluated that quietly surprises anyone who assumes it is fixed at the point of declaration.
Standards & specifications
- Governing standard
- ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)
Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C++ Programming curriculum.
