Back to home

Filed Aug 4, 2026

C++ Function Parameters Explained: Pass-by-Value, `const&`, and Mutable References

C++ Function Parameters Explained: Pass-by-Value, const&, and Mutable References

One of the most frequent pieces of advice you’ll hear in C++ is: “Pass by reference to avoid copying.”

While that advice comes from a good place, taking it too literally can lead to subtle performance issues or overly complex code. C++ gives you precise control over how data enters your functions, and picking the right strategy comes down to two simple questions: How big is the data? and Does the function need to modify it?

The Three Main Parameter Styles

In C++, function parameters broadly fall into three categories:

void byValue(int x);                  // Pass-by-Value (Copy)
void byConstRef(const std::string& s); // Pass-by-Const-Reference (Read-Only)
void byRef(std::vector<int>& vec);     // Pass-by-Reference (Mutable)

1. Pass-by-Value (T)

When you pass a variable by value, C++ creates a brand-new copy of that variable inside the function.

int addOne(int num) {
    num += 1; // Modifies ONLY the local copy inside addOne
    return num;
}

When to use it:

  • Primitive Types: int, float, double, bool, char, and raw pointers.
  • Small Types: Lightweight structs that fit within CPU registers (usually 16 bytes or smaller).
  • std::string_view (C++17+): A lightweight view (pointer + size) designed specifically to be passed by value.

Why not use references for small types?

It might seem tempting to write const int& x, but that can actually hurt performance. Small primitive values fit directly inside CPU registers. Passing an int by value drops it straight into a register, whereas passing by reference (const int&) forces the CPU to pass a memory address and perform an extra memory lookup (dereference) to access the value.

2. Pass-by-Const-Reference (const T&)

Passing by const T& passes a direct reference (memory address) to the original object, while the const modifier guarantees the function cannot modify it.

Example: Processing 100,000 Elements

Consider a function that calculates the sum of a vector holding 100,000 integers:

#include <iostream>
#include <vector>

// Fast and safe: Zero copies created
long long calculateSum(const std::vector<int>& numbers) {
    long long sum = 0;
    for (int num : numbers) {
        sum += num;
    }
    return sum;
}

int main() {
    // Create a vector with 100,000 elements
    std::vector<int> largeList(100000, 42);

    // Passed by const reference: Zero memory allocations
    long long total = calculateSum(largeList);
    
    std::cout << "Total sum: " << total << "\n";
}

Why const T& matters here:

If you wrote calculateSum(std::vector<int> numbers) without the reference &:

  1. C++ would allocate memory on the heap for a second vector.
  2. It would copy all 100,000 integers one by one before the function even starts running.
  3. It would deallocate that memory when the function finishes.

By using const std::vector<int>&, the function receives direct read-only access to largeList in zero time with zero memory overhead.


3. Pass-by-Mutable-Reference (T&)

Passing by mutable reference (T&) gives the function direct access to the original variable in memory. Any changes made inside the function happen directly to the caller’s variable.

How it looks in memory:

Pass-by-Value (int x):
main()   [ score: 10 ]  ──(copies value)──>  fn() [ x: 10 ]

                                               x = 0 (score stays 10)

Pass-by-Reference (int& x):
main()   [ score: 10 ]

             │ (x points directly to score's memory space)
fn()     [   x   ] ────> x = 0 updates main()'s 'score' directly!

Key Use Case: Out-Parameters (Multiple Return Values)

In C++, a return statement can only send back a single value. When you need a function to produce multiple outputs, you can pass caller-owned variables by reference (T&) so the function can fill them in directly—these are known as out-parameters.

#include <iostream>

void getMinMax(int a, int b, int& minOut, int& maxOut) {
    if (a < b) {
        minOut = a;
        maxOut = b;
    } else {
        minOut = b;
        maxOut = a;
    }
}

int main() {
    int minimum = 0;
    int maximum = 0;

    // getMinMax writes its results directly into 'minimum' and 'maximum'
    getMinMax(42, 17, minimum, maximum);

    std::cout << "Min: " << minimum << ", Max: " << maximum << "\n";
    // Output: Min: 17, Max: 42
}