Const References and Read-Only Parameters
The caller sees the change only after the task has written into the data. Until the task finishes, the variable holds whatever value it held before the call.
Predicting a Result Without a Return Value
A task usually exists to produce something, and the caller needs that something back. The most familiar route for getting an answer out of a task is the return value: the task computes an answer and hands it to the caller when it finishes. This article looks at a different route. Instead of sending the answer out through a return value, the caller supplies a reference to data that already exists, and the task writes its outcome into that data.
What do you think happens?
A caller passes its variable to a task by non-const reference, and the task writes a new value into that data. Once the task finishes, what does the caller see in its variable?
Reveal answer
The caller sees the new value, because the task changed the very data the caller named.
When an argument is passed by non-const reference, the task is not working on a private copy. It is working on the data the caller supplied, so any change the task makes is visible to the caller once the task finishes. The answer leaves the task through an input the caller handed over, not through a return value.
Two Routes for Getting an Answer Out
There are two routes by which a task can deliver a result. In the first, the task computes an answer and sends it back through its return value. The caller receives the answer as the value of the call. In the second, the caller passes in a reference to data that already exists, and the task writes its outcome into that data. The answer then arrives in the caller's own variable, even though the call itself may not have returned anything that the caller captured.
The source describes the second route as a somewhat sneaky way of returning a result. It is sneaky because the answer leaves the task by way of an input the caller handed over, which is not where most readers expect an answer to appear. The technique is not uncommon, so you will meet it in real code, even though it is not usually the first choice a careful designer reaches for.
One Object, Two Names
The key to this technique is that the caller and the task do not each hold their own copy of the data. A non-const reference is another name for the same data. The caller's variable and the task's parameter both refer to one object. When the task writes into its parameter, it is writing into that one object, so the caller's variable reflects the change as soon as the task has finished.
Step by Step Through a Call
Consider the order of events. The caller makes the call and passes its variable as the argument. The task now works on that variable through its non-const reference parameter. The task performs its computation and writes the outcome into the parameter. Control then returns to the caller, which can read its own variable and find the result already there. The caller did not have to capture a return value to receive the answer.
The caller sees the change only after the task has written into the data. Until the task finishes, the variable holds whatever value it held before the call.
Judging When a Non-Const Reference Fits
The source ties the decision to intent. A non-const reference should be used only when the task is expected to modify the value of the data it refers to. The non-const reference is the signal that modification is part of the job. Before choosing it, ask whether the task's purpose includes changing the data the caller provides.
Separating the Input from the Output
The source presents a less obscure technique for the same goal. The input data is passed by const reference, so the task can read it but is not meant to change it. A second argument is then added, and this argument names the location where the result should be deposited. The task writes its answer into that location. The result is returned through a separate argument, and the task's job is to deposit the answer where the caller said it should go.
The important difference is what each argument promises. The input is marked as something the task will only examine. The result location is marked as something the task will fill. A reader can see the flow of the answer from the parameter list alone, without reading the body of the task. This clarity is the reason the source calls this route the less obscure one.
Worked Example: Averaging Measurements
The source suggests a concrete design exercise: a task takes a list of measurements and must report their average to the caller. The example below is an original illustration of the design decision, not a source listing.
Choosing a design for an average task
A task must take a list of measurements and make its average available to the caller. Decide which design fits the job.
- 1Ask whether the task modifies the list
The task only needs to read the measurements in order to compute the average. It has no reason to change the list, so the list should be passed by const reference.
- 2Ask whether the answer belongs in the input
The average is a new value derived from the list. It is not a change to the list itself, so it does not belong inside the input data.
- 3Ask whether the call site shows where the result goes
If the result is returned through an output-style argument that is not named clearly, a reader must inspect the body of the task to learn where the average lands. A separately named output argument makes the flow visible from the parameter list.
- 4Pick the design
Pass the measurements by const reference, and pass a separate, explicitly named argument that receives the average. The task reads one argument and fills the other.
The list is read-only, the average has its own clearly named location, and a reader can follow the flow of the answer from the parameter list alone.
Comparing the Three Designs
Three designs can carry an answer from a task to its caller: a return value, a non-const reference that the task writes into, and a const reference for input paired with a separate output location. Each one answers three questions differently: who holds the result, whether the input can change, and how readable the call site is.
| Design | Who holds the result | Can the input change? | Readability of the call site |
|---|---|---|---|
| Return value | The caller receives the answer as the value of the call | Not through the return path; the input is not used to carry the answer | Clear when the answer is captured in a named variable |
| Non-const reference written by the task | The caller's own variable, which the task writes into | Yes, the input is the place where the answer is written | Reader must know the task writes into the argument |
| Const reference for input plus separate output location | The separately named output argument the caller supplied | No, the input is marked read-only | Clear from the parameter list alone |
Three ways to carry a result from a task to its caller, compared on ownership, mutability, and readability
Mistakes With Output Arguments
Common mistake Using a non-const reference for a task that only reads its input
Why it failsThe non-const reference promises a change that never belongs in a read-only task, and the reader cannot tell from the call alone whether the input was altered.
Do this insteadPass the input by const reference, so the task can read it but is not meant to change it.
Common mistake Assuming the task works on a private copy of the caller's data
Why it failsWith a non-const reference, the task is working on the very data the caller named, so its changes are visible to the caller.
Do this insteadRemember that a non-const reference is another name for the caller's data, and treat any write to it as a write to the caller's variable.
Common mistake Hiding the output inside an input argument without a clear name
Why it failsA reader of the call site cannot see where the result goes without reading the body of the task.
Do this insteadAdd a second argument whose name says where the result should be deposited.
Practice: Choosing a Design
A task takes a list of prices and must report the total to the caller. Only read the prices. Which design would you choose, and how would the parameter list show where the total goes?
Hints
- Ask whether the task is expected to modify the list of prices.
- Consider whether the total is part of the input or a separate result.
- A separately named output argument lets a reader find the total from the parameter list alone.
Key Takeaways
- A task can hand back a result by writing into data the caller passed by non-const reference, because the task is working on the caller's own data rather than a copy.
- A non-const reference fits a task whose job is to modify the value it refers to, and is a poor choice for a task that only reads its input.
- Passing the input by const reference and a separate argument that names where the result goes makes the flow of the answer visible from the parameter list.
- The three designs differ in who holds the result, whether the input can change, and how readable the call site is.