const Correctness in Function Parameters
A function can return a result by modifying the data its caller passed in, which works when an argument is taken by nonconst reference.
Why Functions Need a Way to Hand Back Answers
A function usually exists to produce something. It takes inputs, does some work, and the caller needs the outcome. The most familiar route for that outcome is the return value: the function computes an answer and hands it back at the end of the call. Sometimes, though, the design calls for a different route, where the answer is delivered through one of the arguments the caller supplied. This article looks at that alternative, why it works, and when it makes sense to use it.
What do you think happens?
A function receives a variable from its caller and changes it inside the function body. When the function finishes, what does the caller's variable hold?
Reveal answer
The new value, because the function changed the caller's own data
When an argument is passed by nonconst reference, the function does not get a private copy. It gets a name for the caller's own object, so changes made through that name are changes to the original.
Keep one question in mind as you read: when a function hands information back through an argument rather than a return value, which piece of memory is actually being changed, and who ends up seeing the change?
Tracing a Value Through a Nonconst Reference
When an argument is passed by nonconst reference, the function does not receive a private copy of the caller's data. Instead it receives a name for the caller's own object. Anything the function does to that name is done to the original object. This is the core of the technique: the function returns its result by changing the data it was given access to.
points = 50- The parameter score is a name for the caller's points, not a copy
- The function returns no value; the result is left in the caller's object
- After the call, the caller reads points and sees the new value
Notice what did not happen. The function did not send 50 back through a return statement, and the caller did not copy anything out of a return value. The caller's variable simply holds the new value once the function has finished, because the function changed that very object.
A Parameter as a Name for the Caller's Object
The reason the change is visible to the caller is that the caller's argument and the function's parameter both refer to one object. Two names, one object. The parameter is simply another way of reaching the caller's data, so writing through the parameter is writing to the original.
The technique is deliberate rather than accidental. A nonconst reference is appropriate when the job of the function is expected to include modifying the value of the data it refers to. If the function is only meant to read its input, a nonconst reference grants more power than the task needs, and that extra power is a reason to look for a different design.
Splitting Input from Output
A more conventional arrangement splits the jobs apart. The input data is passed by const reference, which tells the reader and the compiler that the function will only look at it. The function's output is handled by a separate argument that marks where the result should be placed. The caller creates that place beforehand, passes its location in, and the function writes the answer there.
total = product of unit_price and count- The const parameters are windows onto the input and must not be changed
- The result parameter is a destination the function is expected to fill
- The caller creates total before the call and reads it afterward
Notice what this arrangement does to the roles of the arguments. One argument is a window onto the input, which the function must not change. The other is a destination, which the function is expected to fill. Because each argument has one clear role, a reader can tell from the parameter list alone which data is read and which data is written.
This approach is described as very common, and it works reliably. Its author does not consider returning results through arguments especially elegant, and the trade-offs are revisited later in the course. For now, treat it as a standard technique you must be able to read and reason about.
Following the Calls Step by Step
Trace the events in order when the caller passes an argument, the function modifies it, and control returns. The sequence below uses the separate-argument version from the previous section, where the caller prepares a destination and the function writes into it.
Tracing the caller's variable through the call
The caller has unit_price equal to 3 and count equal to 4, and wants the product in total using the separate-argument technique. Follow what happens to total.
- 1Caller prepares the destination
The caller sets total to 0 before the call. This is the place the function will deposit the answer.
- 2Caller passes location and inputs
The call supplies
unit_priceand count as const inputs and total as the output argument. - 3Function reads the inputs
Because the input parameters are const, the function only reads 3 and 4 and cannot change them.
- 4Function writes the result
The function computes 12 and writes it into the destination, so total now holds 12.
- 5Caller reads the destination
Control returns, and the caller uses total, which holds 12, with no return value involved.
total holds 12 after the call, and the caller obtained it purely through the output argument.
Choosing Between the Two Techniques
Both techniques place the result somewhere the caller can reach, but they differ in how the function is told where to put it and in what the reader can infer from the signature. The table below sets them side by side.
| Aspect | Returning a result | Depositing into an output argument |
|---|---|---|
| Where the result travels | Back to the caller as the value of the call | Into the destination argument the caller supplied |
| How the function learns the destination | It does not need one; it hands back the answer at the end | The caller creates the destination beforehand and passes its location in |
| Reading the parameter list | The inputs are the parameters; the output is the return type | Each argument has one clear role, so read and written data are visible in the list |
| Typical fit | A single answer the caller uses directly | A design where the caller wants an explicit place for the result and wants inputs visibly protected |
| Main trade-off | Nothing is written into the caller's existing data | The caller must prepare the destination first, and the approach is not considered especially elegant by its author |
Returning a result versus depositing it into a separate output argument
A useful rule of thumb follows from the source. If the task is to change the caller's data as part of its job, a nonconst reference matches the intent. If the task only reads its input, a nonconst reference grants more power than needed, so the separate-argument design, with inputs taken by const reference, is the better fit.
Common mistake Assuming a nonconst reference parameter works on a copy
What it looks likeThe function sets score to 50 and the caller still reads its original 40.
Why it failsA nonconst reference is not a copy. It is a name for the caller's own object.
Do this insteadExpect the caller's variable to hold the new value after the call.
Common mistake Taking input by nonconst reference when the function only reads it
What it looks likeA function that only computes a total still declares its inputs as nonconst references.
Why it failsThis grants the function power to modify the caller's data that the task does not need.
Do this insteadTake read-only inputs by const reference and use a separate argument or a return value for the output.
Common mistake Forgetting to create the destination before the call
What it looks likeThe caller passes an uninitialized variable as the output argument and reads it afterward.
Why it failsThe separate-argument technique relies on the caller creating the place where the answer goes.
Do this insteadCreate the destination variable before the call, then pass it in.
Common mistake Expecting a return value from a function that delivers its result through an argument
Practice Choosing a Design
A function must take a customer's account balance and an interest rate, then update the balance by adding the interest. Which parameter should be nonconst, and which should be const? Explain how the caller will see the result. Then consider a second task: a function that only reports whether a balance is above a threshold. Which technique fits that task, and why?
Hints
- Ask whether the task is expected to change the value of the data it refers to.
- Recall that a nonconst reference is a name for the caller's own object, not a copy.
- A function that only reads its input does not need the extra power of a nonconst reference.
Key Takeaways from the Two Techniques
- 1
- 2
- 3
At a glance
- A function can return a result by modifying the data its caller passed in, which works when an argument is taken by nonconst reference.
- A nonconst reference is not a copy; it is a name for the caller's own object, so changes made through it are visible to the caller after the call.
- The separate-argument technique passes inputs by const reference and uses another argument as the destination, where the caller prepares the place and the function writes the result.
- Choose the nonconst approach when the task is expected to change the data it refers to; choose the separate-argument approach when the function only reads its input and the parameter list should show clearly which data is read and which is written.