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:
Feature | Description |
---|---|
Purpose | Provide input to the function |
Modifiable in Func? | ❌ No (safe from modification) |
Copy Overhead | Depends on type (value = copy; const ref = alias) |
Usage | Default 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:
Feature | Description |
---|---|
Purpose | Send data back to the caller |
Modifiable in Func? | ✅ Yes |
Overwrites input? | Often overwrites; initial value is irrelevant |
Usage | Used 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
Practice | Reason |
---|---|
Suffix out parameters with Out | Improves clarity that the variable will be overwritten |
Place out parameters last | Enhances readability and aligns with common conventions |
Prefer return value for single outputs | Clearer and more idiomatic |
Use reference over pointer if null is not valid | Safer and clearer with modern C++ |
Use const T& for large in parameters | Avoids 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
Type | Modifiable? | Readable? | Preferred Syntax | Use Case |
---|---|---|---|---|
In Parameter | ❌ No | ✅ Yes | T , const T& | Input only |
Out Parameter | ✅ Yes | ❌ No | T& , T* | Output only (multiple values) |
In-Out Parameter | ✅ Yes | ✅ Yes | T& , T* | Modify and read input |
Alternative | ✅ Yes | ✅ Yes | std::tuple , struct | More idiomatic in modern C++ |