CLOSE

When a function is called, it communicates with its caller using two primary mechanisms:

  • Arguments: Sent to the function.
  • Return values: Sent back from the function.

In C++, a function can accept input, return output, or do both. Depending on the purpose, parameters can be categorized as

1 In Parameters (Input Parameters)

Definition:

An in parameter is used to pass data from the caller to the function. It is not modified by the function.

Common Forms:

  • Passed by value
  • Passed by const reference

Characteristics:

FeatureDescription
PurposeProvide input to the function
Modifiable in Func?❌ No (safe from modification)
Copy OverheadDepends on type (value = copy; const ref = alias)
UsageDefault for most scalar and read-only inputs

Example:

#include <iostream>

void print(int x) // x is an in parameter (Pass by value)
{
    std::cout << x << '\n';
}

void print(const std::string& s) // s is an in parameter (Pass by const reference)
{
    std::cout << s << '\n';
}

int main()
{
    print(5);
    std::string s { "Hello, world!" };
    print(s);

    return 0;
}

📌 Best Practice:

Use const T& (const reference) for large user-defined types or std::string to avoid unnecessary copying.

2 Out Parameters (Output Parameters)

Definition:

An out parameter is used to return a value from the function back to the caller using reference or pointer semantics.

A function argument passed by (non-const) reference (or by address) allows the function to modify the value of an object passed as an argument. This provides a way for a function to return data back to the caller in cases where using a return value is not sufficient for some reason.

Common Forms:

  • Passed by non-const reference
  • Passed by pointer

Characteristics:

FeatureDescription
PurposeSend data back to the caller
Modifiable in Func?✅ Yes
Overwrites input?Often overwrites; initial value is irrelevant
UsageUsed when multiple values need to be returned

For example (Reference-based Out Parameters):

#include <cmath>    // for std::sin() and std::cos()
#include <iostream>

// sinOut and cosOut are out parameters
void getSinCos(double degrees, double& sinOut, double& cosOut)
{
    // sin() and cos() take radians, not degrees, so we need to convert
    constexpr double pi { 3.14159265358979323846 }; // the value of pi
    double radians = degrees * pi / 180.0;
    sinOut = std::sin(radians);
    cosOut = std::cos(radians);
}

int main()
{
    double sin { 0.0 };
    double cos { 0.0 };

    double degrees{};
    std::cout << "Enter the number of degrees: ";
    std::cin >> degrees;

    // getSinCos will return the sin and cos in variables sin and cos
    getSinCos(degrees, sin, cos);

    std::cout << "The sin is " << sin << '\n';
    std::cout << "The cos is " << cos << '\n';

    return 0;
}

The function has one parameter degrees (whose argument is passed by value) as input, and returns two parameters (by reference) as output.

We have named these out parameters with the suffix “out” to denote that they are out parameters. This helps remind the the caller that the initial value passed to these parameters doesn't matter, and that we should expect them to be overwritten. By convention, output parameters are typically the rightmost parameters.

Let's explore how this works in more detail. First the main function creates local variables sin and cos. Those are passed into function getSinCos() by reference (rather than by value). This means function getSinCos() has access to the actual sin and cos variables in main(), not just copies. getSinCos() accordingly assigns new values to sin and cos (through references sinOut and cosOut respectively), which overwrites the old values in sin and cos. Function main() then prints these updated values.

If sin and cos had been passed by value instead of reference, getSinCos() would have changed copies of sin and cos, leading to any changes being discarded at the end of the function. But because sin and cos were passed by reference, any changes made to sin or cos (through references) are persisted beyond the function. We can therefore use this mechanism to return values back to the caller.

3 In-Out Parameters

Definition:

A parameter that is both read and modified by the function.

Use Case:

Used when a function needs to read and update a variable.

void increment(int& x) {
    x += 1;
}

In this case, x is both read and modified – hence an in-out parameter.

Best Practices and Conventions

PracticeReason
Suffix out parameters with OutImproves clarity that the variable will be overwritten
Place out parameters lastEnhances readability and aligns with common conventions
Prefer return value for single outputsClearer and more idiomatic
Use reference over pointer if null is not validSafer and clearer with modern C++
Use const T& for large in parametersAvoids unnecessary copies

Alternate Design: Return Struct or Tuple (C++17+)

Instead of using out parameters, consider returning a struct or a std::tuple for multiple outputs:

✅ Returning a Struct:

struct SinCos {
    double sin;
    double cos;
};

SinCos getSinCos(double degrees) {
    constexpr double pi = 3.14159265358979323846;
    double radians = degrees * pi / 180.0;
    return { std::sin(radians), std::cos(radians) };
}

✅ Returning a Tuple:

#include <tuple>

std::tuple<double, double> getSinCos(double degrees) {
    constexpr double pi = 3.14159265358979323846;
    double radians = degrees * pi / 180.0;
    return { std::sin(radians), std::cos(radians) };
}

int main() {
    auto [sine, cosine] = getSinCos(45.0);
}

Benefit: Avoids needing out parameters and keeps function interfaces cleaner.

Summary Table

TypeModifiable?Readable?Preferred SyntaxUse Case
In Parameter❌ No✅ YesT, const T&Input only
Out Parameter✅ Yes❌ NoT&, T*Output only (multiple values)
In-Out Parameter✅ Yes✅ YesT&, T*Modify and read input
Alternative✅ Yes✅ Yesstd::tuple, structMore idiomatic in modern C++