Passing Arguments by Value and by Reference
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 Function That Answers Through Its Arguments
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 follows that alternative step by step, so you can see which piece of memory is changed, and who ends up seeing the change.
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?
The Return Value Route
In the return value route, the function computes its answer and the answer travels back to the caller as the result of the call. The caller decides what to do with it, for example by storing it in a new variable. This route is the baseline against which the argument-based technique is compared later in this article.
What a Nonconst Reference Gives the Function
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.
What do you think happens?
Before reading the trace, predict: after add_ten_by_reference(score) runs, what does score hold in main?
Reveal answer
15
The parameter n is a nonconst reference, so n is just another name for score. Writing to n writes to score itself.
After the by-value call, score is 5. After the by-reference call, score is 15.- int n in
add_ten_by_valuereceives a copy of score's value - int& n in
add_ten_by_referencereceives a name for score itself - Both function bodies are identical; only the parameter type differs
Read the two functions as two different contracts. The by-value version can do whatever it likes to n, but the caller never sees it. The by-reference version writes through to the caller's object, so the caller's variable holds the new value once the call ends. The function body looks the same in both cases; the parameter type is what decides where the change lands.
The diagram shows the same starting point, a caller's variable holding 5, splitting into two outcomes. The by-value path leaves the caller's variable untouched because the function was working on a separate copy. The by-reference path changes the caller's variable itself, which is why the reference technique is described as deliberate rather than accidental.
When a Nonconst Reference Is Justified
A nonconst reference is appropriate when the job of the function is expected to include modifying the value of the data it refers to. That is the only reason to choose it. 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.
The Argument as an Output Slot
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.
Notice what this 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.
room_area holds 12.0 after the call- width and height are const references: read-only windows onto the inputs
- area is a nonconst reference: the destination the function fills
- The caller creates
room_areabefore the call and passes it in
Trace the example in the order the diagram shows. main declares room_area first, so the destination exists before the call. The call passes it in alongside the two inputs. Inside compute_area, the only write goes to area, which is room_area under another name. When the function returns, main reads room_area and finds 12.0. No return value carried the answer; the destination did.
Where the Result Ends Up Under Each Technique
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 and diagram below set them side by side.
| Question | Return value | Output argument |
|---|---|---|
| Where does the result come out? | As the value of the call | In the argument the caller supplied |
| What must the caller prepare? | Nothing beyond receiving the return | A variable to receive the result |
| What does the signature reveal? | The result type on the left of the function name | Which parameters are read-only and which are written |
| Which argument form is typical? | Inputs of any passing style | Inputs by const reference, output by nonconst reference |
Return value versus argument-based output
The two columns answer the same question with different mechanics. The return value route keeps the caller's obligations small, since the result simply arrives. The argument route asks the caller to create the destination in advance, but in exchange the parameter list announces which data is read and which is written.
Choosing Between the Two Techniques
Choosing a technique for a new task
You need a function that takes a width and a height and produces the area. Should it return the area, or write it into an argument?
- 1Identify the job
The function produces one answer and only reads its inputs.
- 2Check whether any caller-owned data must change
Nothing the caller owns needs modifying beyond receiving the answer, so a nonconst reference would grant power the task does not need.
- 3Weigh the signature
A return value keeps the call expression self-contained, which tends to read most naturally for a single answer.
- 4Decide
Return the area. The output-argument technique is valid, but here it adds a caller-side setup step without a clear gain.
Return the area. Reserve the output-argument technique for cases where the design deliberately separates reading inputs from depositing results, and where the parameter list's explicit roles are worth the extra setup.
Use an output argument when the parameter list should announce that a value is being deposited, and when the caller already has, or naturally creates, a destination to receive it. Pass inputs by const reference so the reader and the compiler both know the function will not change them. Use a nonconst reference only when modifying the caller's data is part of the function's job.
The course treats the output-argument technique as a standard technique you must be able to read and reason about, not as an especially elegant one. Its place is revisited later, when the trade-offs can be weighed more fully. For now, the goal is recognizing it when you see it and understanding exactly where its result lands.
Mistakes Beginners Make with Reference Arguments
Common mistake Expecting a by-value argument to change the caller's variable
What it looks likevoid add_ten_by_value(int n) { n = n + 10; } is called with score, then score is printed expecting 15.Why it failsA by-value parameter receives a separate copy of the caller's value, so writes to it never reach the caller's object.
Do this insteadTake the parameter by nonconst reference if the function must change the caller's variable, as in int& n.
Common mistake Making a reference parameter nonconst when the function only reads it
What it looks likevoid show_area(double& width) that only prints width.Why it failsA nonconst reference grants permission to modify the caller's data that the task does not need, and it hides the function's read-only intent.
Do this insteadUse const double& width when the function only reads its input.
Common mistake Passing an output argument without creating a destination first
Why it failsThe function writes into the argument it was given, so the caller must have a variable ready to receive the result.
Do this insteadDeclare the destination variable before the call, such as double room_area = 0.0;, and pass it in.
Common mistake Forgetting that a call that writes an output argument returns nothing useful for the result
Practice: Tracing and Choosing
A function double_it(int& x) doubles x. The caller sets int count = 4; then calls double_it(count); twice in a row. What does count hold afterward, and which trace step explains it?
Hints
- Ask whether x is a copy of count or another name for it.
- Track the value after each call separately.
You must write a function that reads a temperature in Celsius and produces the matching Fahrenheit value. Decide whether to return the result or write it into a destination argument. Justify your choice using what the parameter list would reveal to a reader, and state which inputs should be passed by const reference.
Hints
- Compare the caller's obligations under each technique.
- Consider whether anything the caller owns must change.
Key Takeaways
- A function can deliver a result by changing data that belongs to its caller, which works when the argument is taken by nonconst reference.
- A nonconst reference is a name for the caller's own object, not a copy, so anything the function does to it changes the original.
- The output-argument technique passes inputs by const reference and supplies a separate destination argument, separating reading from writing so each argument's role is visible in the parameter list.
- Choose a return value for a single answer that only needs reading inputs; choose an output argument when the design deliberately separates inputs from a deposited result and the caller can supply the destination.