Concepts / Passing Arguments by Value and by Reference

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.

  • Programming

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?

  • 5

  • 15

  • It is undefined

Reveal answer
Answer:

15

The parameter n is a nonconst reference, so n is just another name for score. Writing to n writes to score itself.

cpp
Output
After the by-value call, score is 5. After the by-reference call, score is 15.
Notes on the code
  • int n in add_ten_by_value receives a copy of score's value
  • int& n in add_ten_by_reference receives 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.

CallermainFunctioncompute_arearoom_areaoutput slot1. call, passing room_area as output2. writes 12.0 into the slot3. call returns, no result value4. caller reads 12.0
In what order do the caller's declaration, the call, the function's write, and the caller's read happen, and where does the result end up?
cpp
Output
room_area holds 12.0 after the call
Notes on the code
  • 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_area before 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.

QuestionReturn valueOutput argument
Where does the result come out?As the value of the callIn the argument the caller supplied
What must the caller prepare?Nothing beyond receiving the returnA variable to receive the result
What does the signature reveal?The result type on the left of the function nameWhich parameters are read-only and which are written
Which argument form is typical?Inputs of any passing styleInputs by const reference, output by nonconst reference

Return value versus argument-based output

Result locationreturned to callerResult locationa nonconst referenceargumentCaller preparesa variable to store thereturnCaller preparesa destination variableSignature showsresult type before the nameSignature showsread-only and writtenparameters
How do the two techniques differ in what the caller must write, what the function delivers, and which is clearer or less error-prone for a given task?

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

Worked example

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?

  1. 1
    Identify the job

    The function produces one answer and only reads its inputs.

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

  3. 3
    Weigh the signature

    A return value keeps the call expression self-contained, which tends to read most naturally for a single answer.

  4. 4
    Decide

    Return the area. The output-argument technique is valid, but here it adds a caller-side setup step without a clear gain.

Result

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.

Best practice

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 like
    void add_ten_by_value(int n) { n = n + 10; } is called with score, then score is printed expecting 15.
    Why it fails

    A by-value parameter receives a separate copy of the caller's value, so writes to it never reach the caller's object.

    Do this instead

    Take 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 like
    void show_area(double& width) that only prints width.
    Why it fails

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

    Use const double& width when the function only reads its input.

  • Common mistake

    Passing an output argument without creating a destination first

    Why it fails

    The function writes into the argument it was given, so the caller must have a variable ready to receive the result.

    Do this instead

    Declare 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

EASY

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.
MEDIUM

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.

Related concepts

More concepts starting with P