Concepts / const Correctness in Function Parameters

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.

  • Programming

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?

  • The original value, because the function worked on a private copy

  • The new value, because the function changed the caller's own data

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

Output (expected)
points = 50
Notes on the code
  • 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
points before call40points after call50add_bonus modifies the object
What changes when add_bonus runs on points: the caller's variable goes from 40 to 50 without any value being returned.

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.

pointscaller's nameshared objectvalue 50 after the callscoreparameter's namenamesalso names
The caller's variable points points at the object, and the parameter score refers to that same object, so a change made through score is visible through points.

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.

Output (expected)
total = product of unit_price and count
Notes on the code
  • 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.

callercompute_total1. pass inputs and destination2. write product into destination3. return with destination filled
The caller passes its input and its destination, the function writes the result into the destination, and control returns with the result already in place.
Worked example

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.

  1. 1
    Caller prepares the destination

    The caller sets total to 0 before the call. This is the place the function will deposit the answer.

  2. 2
    Caller passes location and inputs

    The call supplies unit_price and count as const inputs and total as the output argument.

  3. 3
    Function reads the inputs

    Because the input parameters are const, the function only reads 3 and 4 and cannot change them.

  4. 4
    Function writes the result

    The function computes 12 and writes it into the destination, so total now holds 12.

  5. 5
    Caller reads the destination

    Control returns, and the caller uses total, which holds 12, with no return value involved.

Result

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.

AspectReturning a resultDepositing into an output argument
Where the result travelsBack to the caller as the value of the callInto the destination argument the caller supplied
How the function learns the destinationIt does not need one; it hands back the answer at the endThe caller creates the destination beforehand and passes its location in
Reading the parameter listThe inputs are the parameters; the output is the return typeEach argument has one clear role, so read and written data are visible in the list
Typical fitA single answer the caller uses directlyA design where the caller wants an explicit place for the result and wants inputs visibly protected
Main trade-offNothing is written into the caller's existing dataThe 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 like

    The function sets score to 50 and the caller still reads its original 40.

    Why it fails

    A nonconst reference is not a copy. It is a name for the caller's own object.

    Do this instead

    Expect 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 like

    A function that only computes a total still declares its inputs as nonconst references.

    Why it fails

    This grants the function power to modify the caller's data that the task does not need.

    Do this instead

    Take 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 like

    The caller passes an uninitialized variable as the output argument and reads it afterward.

    Why it fails

    The separate-argument technique relies on the caller creating the place where the answer goes.

    Do this instead

    Create 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

MEDIUM

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. 1
  2. 2
  3. 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.

Related concepts

More concepts starting with C