How can functions return several values?

Consider the problem of computing the maximum, the minimum, and the average of a list of integers stored in a vector. One can then have three different functions, each computing and returning one of these values.

// Return the minimum value stored in V
int min(const std::vector<int>& V);

// Return the maximum value stored in V
int max(const std::vector<int>& V);

// Return the average of the values stored in V
double average(const std::vector<int>& V);

Each of these functions loops through the values stored in the vector, like

for(int e : V){
   //Do something
}

Thus, if one uses the functions above to compute the maximum, the minimum, and the average of the values stored in a vector then one has to loop through the vector three times. One can, however, think of a more time efficient function: loop once through the vector, compute all three values, and return them. A sketch of the function could look something like the following (not yet compilable C++):

// Return the min, the max, and the average of the values in V
???? max_min_avg(const std::vector<int>& V) { // how can a function return several values??
    int min = V[0];
    int max = V[0];

    double sum = 0.0;

    for (int e : V) {
        if (e < min) {
            min = e;
        } else if (e > max) {
            max = e;
        }

        sum += e;
    }

    return ???? // min, max, sum / V.size() ;
}

The problem to put this idea in practice is that functions can only return one value (or nothing, void). So, how can one write a function that returns three values? A first idea could be to store the three values, the maximum, the minimum, and the average, in a vector of three slots. The problem is that all values stored in a vector must be of the same type, and in our case we have two values of type int (the the maximum and the minimum) and one value of type double (the average). So vectors are not going to help us.

One way to address this problem is to use tuples.

Tuples

As you know from your math courses, tuples are collections of possibly heterogeneous values like $\langle 6.0$, $2.5$, $9$, “ok”$\rangle$ or $\langle-1$, $56.66$, “Anna Lind”$\rangle$. These tuples contain integer values, real numbers, and text. Of course, all values in a tuple may also be of the same type, as in $\langle-1, -2, 8, 10\rangle$ where all values are integers.

The C++-standard library offers the type std::tuple which allows the programmer to define variables that store tuples. For instance, the three tuple examples given above could be translated to C++ as follows.

#include <tuple>
#include <string>

std::tuple<double, double, int, std::string> t1(6.0, 2.5, 9, "ok");
std::tuple<int, double, std::string> t2(-1, 5.66, "Anna Lind");
std::tuple<int, int, int, int> t3(-1, -2, 8, 10);

First, it is required to include the library <tuple> in order to be able to use tuples in a program. Variable t1 stores a tuple of four-values: the first and second elements are of type double, the third element is of type int, and the fourth element is of type std::string. Note that std::string is a type defined in the C++-standard library to represent text and the library <string> should then be included, as well.

To access the different elements of a tuple, one can use a modern technique named structured-binding. To exemplify the use of this technique, imagine that the elements in tuple t1 above represent the temperature in two different cities (Stockholm and Uppsala), the month when the temperatures were measured at those locations (month $9$, i.e. September), and the status of the sensor that measured the temperatures (“ok”). The program below accesses these values and displays them to the user. Note that to be able to compile this program, you need a compiler that compiles C++17 or C++20.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <tuple>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> months_name = {"January",   "February", "March",    "April",
                                            "May",       "June",     "July",     "August",
                                            "September", "October",  "November", "December"};

    std::tuple<double, double, int, std::string> t1(6.0, 2.5, 9, "ok");  // a tuple

    auto [temp_sto, temp_upp, month, sensor_status] = t1;  // structured-binding

    std::cout << "Temperature in Stockholm: " << temp_sto << "\n";
    std::cout << "Temperature in Uppsala: " << temp_upp << "\n";
    std::cout << "Month: " << months_name[month - 1] << "\n";
    std::cout << "Sensor status: " << sensor_status << "\n";
}

The structured-binding in line $13$ above defines four variables and initializes them with an element of tuple t1: variable temp_sto of type double is initialized with the first element of the tuple (6.0); variable temp_upp of type double is initialized with the second element of the tuple (2.5); variable month of type int is initialized with the third element of the tuple (9); finally, variable sensor_status of type std::string is initialized with the fouth element of the tuple ("ok").

Example

Let us go back to our initial problem: to define a function that returns the minimum, the maximum , and the average of the values stored in a given vector. Functions in C++ can return return a value of any type, like a value of type std::tuple. Thus, after computing the minimum, the maximum, and the average, the function can create a tuple with these three values and then return the tuple.

// Return a tuple with the min, the max, and the average of the values in V
// Assume V.size() > 0
std::tuple<int, int, double> max_min_avg(const std::vector<int>& V) {
    int min = V[0];
    int max = V[0];

    double sum = 0.0;

    for (int e : V) {
        if (e < min) {
            min = e;
        } else if (e > max) {
            max = e;
        }

        sum += e;
    }
	
	/*
	std::tuple<int, int, double> t1(min, max, sum / V.size());
	return t1;
	*/
	
    return std::tuple(min, max, sum / V.size()); // return a tuple of <int, int, double>
}

Another alternative is to write a function that returns a struct. C++ allows programmers to define new data types with struct. This concept is introduced in the section struct.