tuple
to Return Multiple ValuesA common use of tuple
is to return multiple values from a function. For example, our bookstore might be one of several stores in a chain. Each store would have a transaction file that holds data on each book that the store recently sold. We might want to look at the sales for a given book in all the stores.
We’ll assume that we have a file of transactions for each store. Each of these per-store transaction files will contain all the transactions for each book grouped together. We’ll further assume that some other function reads these transaction files, builds a vector<Sales_data>
for each store, and puts those vector
s in a vector
of vector
s:
// each element in files holds the transactions for a particular store
vector<vector<Sales_data>> files;
We’ll write a function that will search files
looking for the stores that sold a given book. For each store that has a matching transaction, we’ll create a tuple
to hold the index of that store and two iterators. The index will be the position of the matching store in files
. The iterators will mark the first and one past the last record for the given book in that store’s vector<Sales_data>
.
tuple
We’ll start by writing the function to find a given book. This function’s arguments are the vector
of vector
s just described, and a string
that represents the book’s ISBN. Our function will return a vector
of tuple
s that will have an entry for each store with at least one sale for the given book:
// matches has three members: an index of a store and iterators into that store's vector
typedef tuple<vector<Sales_data>::size_type,
vector<Sales_data>::const_iterator,
vector<Sales_data>::const_iterator> matches;
// files holds the transactions for every store
// findBook returns a vector with an entry for each store that sold the given book
vector<matches>
findBook(const vector<vector<Sales_data>> &files,
const string &book)
{
vector<matches> ret; // initially empty
// for each store find the range of matching books, if any
for (auto it = files.cbegin(); it != files.cend(); ++it) {
// find the range of Sales_data that have the same ISBN
auto found = equal_range(it->cbegin(), it->cend(),
book, compareIsbn);
if (found.first != found.second) // this store had sales
// remember the index of this store and the matching range
ret.push_back(make_tuple(it - files.cbegin(),
found.first, found.second));
}
return ret; // empty if no matches found
}
The for
loop iterates through the elements in files
. Those elements are themselves vector
s. Inside the for
we call a library algorithm named equal_range
, which operates like the associative container member of the same name (§ 11.3.5, p. 439). The first two arguments to equal_range
are iterators denoting an input sequence (§ 10.1, p. 376). The third argument is a value. By default, equal_range
uses the <
operator to compare elements. Because Sales_data
does not have a <
operator, we pass a pointer to the compareIsbn
function (§ 11.2.2, p. 425).
The equal_range
algorithm returns a pair
of iterators that denote a range of elements. If book
is not found, then the iterators will be equal, indicating that the range is empty. Otherwise, the first
member of the returned pair
will denote the first matching transaction and second
will be one past the last.
tuple
Returned by a FunctionOnce we have built our vector
of stores with matching transactions, we need to process these transactions. In this program, we’ll report the total sales results for each store that has a matching sale:
void reportResults(istream &in, ostream &os,
const vector<vector<Sales_data>> &files)
{
string s; // book to look for
while (in >> s) {
auto trans = findBook(files, s); // stores that sold this book
if (trans.empty()) {
cout << s << " not found in any stores" << endl;
continue; // get the next book to look for
}
for (const auto &store : trans) // for every store with a sale
// get<n> returns the specified member from the tuple in store
os << "store " << get<0>(store) << " sales: "
<< accumulate(get<1>(store), get<2>(store),
Sales_data(s))
<< endl;
}
}
The while
loop repeatedly reads the istream
named in
to get the next book to process. We call findBook
to see if s
is present, and assign the results to trans
. We use auto
to simplify writing the type of trans
, which is a vector
of tuple
s.
If trans
is empty, there were no sales for s
. In this case, we print a message and return to the while
to get the next book to look for.
The for
loop binds store
to each element in trans
. Because we don’t intend to change the elements in trans
, we declare store
as a reference to const
. We use get
to print the relevant data: get<0>
is the index of the corresponding store, get<1>
is the iterator denoting the first transaction, and get<2>
is the iterator one past the last.
Because Sales_data
defines the addition operator (§ 14.3, p. 560), we can use the library accumulate
algorithm (§ 10.2.1, p. 379) to sum the transactions. We pass a Sales_data
object initialized by the Sales_data
constructor that takes a string
(§ 7.1.4, p. 264) as the starting point for the summation. That constructor initializes the bookNo
member from the given string
and the units_sold
and revenue
members to zero.
Exercise 17.4: Write and test your own version of the findBook
function.
Exercise 17.5: Rewrite findBook
to return a pair
that holds an index and a pair
of iterators.
Exercise 17.6: Rewrite findBook
so that it does not use tuple
or pair
.
Exercise 17.7: Explain which version of findBook
you prefer and why.
Exercise 17.8: What would happen if we passed Sales_data()
as the third parameter to accumulate
in the last code example in this section?