Docs

README

Function Parameters in C++

Table of Contents

  1. Introduction
  2. Parameter Passing Methods
  3. Pass by Value
  4. Pass by Reference
  5. Pass by Pointer
  6. Const Parameters
  7. Default Arguments
  8. Array Parameters
  9. Best Practices

Introduction

Function parameters define how data flows into functions. C++ offers multiple parameter-passing mechanisms, each with distinct performance and safety characteristics.

Core Question: Should the function read, modify, or own the data?

IntentRecommended Approach
Read only (small type)Pass by value
Read only (large type)const T&
Modify caller's dataT& (reference)
Optional/nullableT* (pointer)
Transfer ownershipT&& or smart pointer

Parameter Passing Methods

Overview Diagram

┌─────────────────────────────────────────────────────────────┐
│                  PARAMETER PASSING IN C++                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │  BY VALUE   │  │ BY REFERENCE│  │  BY POINTER │         │
│  │   (copy)    │  │  (alias)    │  │  (address)  │         │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘         │
│         │                │                │                 │
│    void f(int x)    void f(int &x)   void f(int *x)        │
│         │                │                │                 │
│   Creates copy     Same memory      Holds address          │
│   Changes local    Changes original Can be nullptr         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Pass by Value

The function receives a copy of the argument. Changes inside the function don't affect the original.

Syntax

void func(int x);      // x is a copy
void func(string s);   // s is a copy (expensive for large strings!)

When to Use

  • Small, cheap-to-copy types (int, double, char, bool)
  • When function needs its own copy to modify
  • When you want to guarantee caller's data is unchanged

Example

void doubleValue(int x) {
    x = x * 2;  // Only affects local copy
    cout << "Inside: " << x << endl;
}

int main() {
    int num = 5;
    doubleValue(num);
    cout << "Outside: " << num << endl;  // Still 5!
}

Performance Note

// INEFFICIENT - copies entire vector!
void process(vector<int> data) { ... }

// EFFICIENT - no copy
void process(const vector<int>& data) { ... }

Pass by Reference

The function receives an alias to the original variable. Changes affect the caller's data.

Syntax

void func(int& x);         // Reference to int
void func(string& s);      // Reference to string
void func(vector<int>& v); // Reference to vector

When to Use

  • Function needs to modify caller's variable
  • Avoid copying large objects
  • Returning multiple values (out parameters)

Example: Swap Function

void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 10, y = 20;
    swap(x, y);
    cout << x << ", " << y << endl;  // 20, 10
}

Example: Multiple Outputs

void minMax(const vector<int>& nums, int& outMin, int& outMax) {
    if (nums.empty()) return;
    outMin = outMax = nums[0];
    for (int n : nums) {
        if (n < outMin) outMin = n;
        if (n > outMax) outMax = n;
    }
}

int main() {
    vector<int> data = {3, 1, 4, 1, 5, 9};
    int min, max;
    minMax(data, min, max);
    cout << "Min: " << min << ", Max: " << max << endl;
}

Pass by Pointer

The function receives the memory address of the variable. Similar to reference but can be null.

Syntax

void func(int* ptr);        // Pointer to int
void func(const int* ptr);  // Pointer to const int (read-only)
void func(int* const ptr);  // Const pointer (can't reassign)

When to Use

  • Parameter is optional (can pass nullptr)
  • Working with arrays
  • Interfacing with C code
  • Dynamic memory management

Example: Optional Output

bool divide(int a, int b, double* result) {
    if (b == 0) return false;
    if (result) {  // Check for nullptr
        *result = static_cast<double>(a) / b;
    }
    return true;
}

int main() {
    double res;
    if (divide(10, 3, &res)) {
        cout << "Result: " << res << endl;
    }

    // Just check if divisible, don't need result
    if (divide(10, 0, nullptr)) {
        cout << "Divisible" << endl;
    }
}

Pointer vs Reference Comparison

FeatureReferencePointer
Can be nullNoYes
Can be reassignedNoYes
SyntaxCleanerRequires * and &
Must be initializedYesNo

Const Parameters

The const keyword prevents modification, enabling optimization and catching errors.

Const Reference (Most Common)

// Read-only access, no copy
void print(const string& s) {
    cout << s << endl;
    // s = "new";  // ERROR: can't modify const
}

void process(const vector<int>& data) {
    for (int n : data) {
        cout << n << " ";
    }
    // data.push_back(1);  // ERROR
}

Const Pointer Variations

void f1(const int* p);   // Can't modify *p, can reassign p
void f2(int* const p);   // Can modify *p, can't reassign p
void f3(const int* const p);  // Can't modify either

Visual Guide

const int* ptr        →  "pointer to const int"    →  *ptr is read-only
int* const ptr        →  "const pointer to int"    →  ptr is read-only
const int* const ptr  →  both are read-only

Default Arguments

Provide default values for parameters that are optional.

Syntax Rules

// Defaults must be rightmost parameters
void greet(string name, string greeting = "Hello", int times = 1);

// INVALID: default before non-default
// void greet(string greeting = "Hello", string name);  // ERROR

Example

void printMessage(const string& msg,
                  int count = 1,
                  const string& prefix = ">> ") {
    for (int i = 0; i < count; i++) {
        cout << prefix << msg << endl;
    }
}

int main() {
    printMessage("Hello");              // Uses all defaults
    printMessage("Hello", 3);           // Custom count
    printMessage("Hello", 2, "!! ");    // All custom
}

Important Rules

  1. Defaults in declaration (header), not definition
  2. Right-to-left ordering required
  3. Once a default is used, all following must have defaults
// header.h
void connect(string host, int port = 80, bool secure = false);

// source.cpp
void connect(string host, int port, bool secure) {
    // Don't repeat defaults here!
}

Array Parameters

Arrays decay to pointers when passed to functions.

Array Decay

void process(int arr[]);     // Actually: void process(int* arr)
void process(int arr[10]);   // Still just a pointer! Size ignored.
void process(int* arr);      // Most honest declaration

Passing Array with Size

void printArray(const int* arr, size_t size) {
    for (size_t i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

int main() {
    int nums[] = {1, 2, 3, 4, 5};
    printArray(nums, 5);
}

Modern Alternative: std::span (C++20)

#include <span>
void process(std::span<int> arr) {
    for (int n : arr) {
        cout << n << " ";
    }
}

Reference to Array (Preserves Size)

template<size_t N>
void processFixed(int (&arr)[N]) {
    cout << "Array size: " << N << endl;
    for (int i = 0; i < N; i++) {
        cout << arr[i] << " ";
    }
}

Best Practices

Decision Flowchart

                    ┌─────────────────────┐
                    │ Choosing Parameter  │
                    │       Type          │
                    └──────────┬──────────┘
                               │
                    ┌──────────▼──────────┐
                    │ Need to modify      │
                    │ caller's data?      │
                    └──────────┬──────────┘
                          YES  │  NO
                    ┌──────────┴──────────┐
                    │                     │
              ┌─────▼─────┐        ┌──────▼──────┐
              │ Can be    │        │ Small type? │
              │ nullptr?  │        │ (int, etc.) │
              └─────┬─────┘        └──────┬──────┘
               YES  │  NO            YES  │  NO
              ┌─────┴──────┐       ┌──────┴──────┐
              │            │       │             │
          ┌───▼───┐  ┌─────▼────┐ ┌▼──────┐ ┌────▼────┐
          │  T*   │  │   T&     │ │ T     │ │const T& │
          └───────┘  └──────────┘ │(value)│ └─────────┘
                                  └───────┘

Guidelines Summary

ScenarioUse
Small built-in type, read-onlyint x
Small built-in type, modifyint& x
Large object, read-onlyconst T& x
Large object, modifyT& x
Optional parameterT* x
Ownership transferunique_ptr<T> or T&&

Code Examples

// ✓ Good
void process(const vector<int>& data);
void update(string& name);
bool find(const map<int,int>& m, int key, int* outValue);

// ✗ Avoid
void process(vector<int> data);  // Unnecessary copy
void update(string* name);       // Use reference if never null

Quick Reference Card

// VALUE - copy, safe, may be slow for large types
void f(int x);

// REFERENCE - alias, modifies original
void f(int& x);

// CONST REFERENCE - alias, read-only, efficient
void f(const string& x);

// POINTER - can be null, explicit dereferencing
void f(int* x);

// CONST POINTER - read-only access
void f(const int* x);

// DEFAULT - optional parameters
void f(int x, int y = 0);

Compile & Run

g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples
README - C++ Tutorial | DeepML