Concepts / Const References and Read-Only Parameters

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.

  • Programming

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?

  • The old value, because the task only worked on a private copy

  • The new value, because the task changed the very data the caller named

  • Nothing, because a task cannot change data it did not return

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

caller variablename in the callershared datavalue after task writesresulttask parameternon-const reference namerefers torefers to
Before the call, the caller's name and the task's parameter name both refer to one stored value. The task writes the result into that value, so after the call the caller reads the new value through its own name. The stored value changes; the two names do not need to change.

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.

Callerowns the variableTaskreceives non-constreference1. call with variable as argument2. write result into the argument3. control returns4. read variable and see new value
The caller hands its variable to the task, the task writes the result into that same data, and the caller reads the result afterwards. The order of messages matters: the caller only sees the new value after the task has finished writing.
Key point

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.

input datapassed by const referencetaskreads input, fills outputresult locationseparate named argumentread onlydeposit answer
The input flows into the task only for reading. The task sends its answer to a separate, explicitly named output location. The parameter list alone shows where the answer goes.

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.

Worked example

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.

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

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

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

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

Result

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.

DesignWho holds the resultCan the input change?Readability of the call site
Return valueThe caller receives the answer as the value of the callNot through the return path; the input is not used to carry the answerClear when the answer is captured in a named variable
Non-const reference written by the taskThe caller's own variable, which the task writes intoYes, the input is the place where the answer is writtenReader must know the task writes into the argument
Const reference for input plus separate output locationThe separately named output argument the caller suppliedNo, the input is marked read-onlyClear 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 fails

    The 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 instead

    Pass 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 fails

    With 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 instead

    Remember 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 fails

    A reader of the call site cannot see where the result goes without reading the body of the task.

    Do this instead

    Add a second argument whose name says where the result should be deposited.

Practice: Choosing a Design

EASY

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.

Related concepts

More concepts starting with C