Module 1 ¡ From C to Just-Enough C++
pair and tuple: Two or More Things With One Name
In this lesson
- Make a
std::pair, read its.firstand.second, and say in which order two pairs compare and sort. - Make a
std::tuple, read it withstd::getand with a structured binding, and return two or three values from one function. - Choose between a pair and a struct with named fields, and read GCC 12's messages for the four usual pair mistakes.
Zara needs the coldest reading of the week and the day it happened. In C, a function returns one value. So she returned the reading and passed a pointer for the day, as lesson 03's exercise did with references. C++ has a smaller answer: put the two values in one package and return the package. The package is called a pair, and the STL hands you pairs everywhere, from every map to every sort of two numbers.
Zara's two answers from one function
Here is the C way, with the second answer coming back through a pointer.
#include <stdio.h>
int coldest(const int temps[], int n, int *day)
{
int best = temps[0];
*day = 0;
for (int i = 1; i < n; i++) {
if (temps[i] < best) {
best = temps[i];
*day = i;
}
}
return best;
}
int main(void)
{
int temps[7] = {4, -2, 1, -6, 0, -6, 3};
int day = 0;
int t = coldest(temps, 7, &day);
printf("coldest %d on day %d\n", t, day + 1);
return 0;
}
coldest -6 on day 4
And here is C++, where the function returns both answers as one std::pair.
#include <iostream>
#include <utility>
std::pair<int, int> coldest(const int temps[], int n)
{
int best = temps[0];
int day = 0;
for (int i = 1; i < n; i++) {
if (temps[i] < best) {
best = temps[i];
day = i;
}
}
return {best, day};
}
int main()
{
int temps[7] = {4, -2, 1, -6, 0, -6, 3};
auto [t, day] = coldest(temps, 7);
std::cout << "coldest " << t << " on day " << day + 1 << '\n';
return 0;
}
coldest -6 on day 4
Same output. return {best, day}; packs the two values, and auto [t, day] = ... unpacks them into two names; both are explained below. The -6 on day 6 does not win, because < only replaces the best on a strictly colder day. So one function now returns two answers, with no pointer and nothing to remember to pass in.
A pair: two values under one name
A std::pair holds exactly two values, possibly of two different types, under one name. It lives in <utility>. Its two parts are always called .first and .second, and they are ordinary variables you can read and write. It is a class template (lesson 05), filled in with the two types.
Making and reading a pair
std::pair<Type1, Type2> p = {value1, value2};
auto q = std::make_pair(value1, value2);
p.first p.second
std::pair<Type1, Type2>is the pair filled in with two types, such asstd::pair<std::string, int>.{value1, value2}fills it, first value first.std::make_pairbuilds a pair and lets the types come from the values, handy withauto..firstand.secondare the two parts, with no brackets after them.
#include <iostream>
#include <string>
#include <utility>
int main()
{
std::pair<std::string, int> city = {"Oslo", -3};
auto other = std::make_pair(std::string("Cairo"), 31);
std::cout << city.first << ": " << city.second << '\n';
city.second = -1;
std::cout << city.first << " at noon: " << city.second << '\n';
std::cout << other.first << ": " << other.second << '\n';
return 0;
}
Oslo: -3
Oslo at noon: -1
Cairo: 31
So a pair is two named boxes travelling together: .first and .second, read and changed like any variable.
How two pairs compare, and how they sort
Two pairs of the same type compare the way a dictionary compares words. First the .first parts decide. Only when they are equal do the .second parts decide. This is called lexicographic order.
#include <iostream>
#include <utility>
int main()
{
std::pair<int, int> a = {1, 9};
std::pair<int, int> b = {2, 0};
std::pair<int, int> c = {2, 5};
std::cout << (a < b) << ' ' << (b < c) << ' ' << (c == b) << '\n';
return 0;
}
1 1 0
a < b is true because 1 is less than 2, and the 9 is never looked at. b < c is true because the firsts tie at 2 and then 0 is less than 5. == needs both parts equal.
That order is what makes pairs sort sensibly. Here is one sort, previewed: std::sort (Module 12) on a std::vector (Module 2), an array that knows its own size. Kenji's scoreboard sorts by name, and a name that appears twice is ordered by score.
#include <algorithm>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
int main()
{
std::vector<std::pair<std::string, int>> board = {
{"Zara", 40}, {"Bob", 75}, {"Amara", 60}, {"Bob", 20}
};
std::sort(board.begin(), board.end());
for (const auto& p : board) {
std::cout << p.first << ' ' << p.second << '\n';
}
return 0;
}
Amara 60
Bob 20
Bob 75
Zara 40
The two Bobs tie on the name, so their scores decide: 20 before 75. So pairs compare first by .first and then by .second, and a sort of pairs uses exactly that order with no extra code.
tuple: three or more, and std::get
A std::tuple is a pair that can hold any number of values. It lives in <tuple>. Its parts have no names like .first; they are numbered from 0, and std::get<0>(t) reads part 0. The number goes in angle brackets because the compiler must know it while compiling, to know the part's type.
#include <iostream>
#include <string>
#include <tuple>
int main()
{
std::tuple<std::string, int, double> book = {"Dune", 1965, 4.5};
std::cout << std::get<0>(book) << " (" << std::get<1>(book) << "), rated " << std::get<2>(book) << '\n';
std::get<2>(book) = 4.8;
std::cout << "new rating " << std::get<2>(book) << '\n';
return 0;
}
Dune (1965), rated 4.5
new rating 4.8
Tuples compare and sort the same lexicographic way as pairs, part 0 first. So a tuple is a pair with more parts, numbered instead of named, and std::get<N> reaches part N.
Structured bindings: a name for every part
std::get<2>(book) does not say what part 2 is. C++17 added structured bindings: one declaration that gives every part of a pair, a tuple or an array its own name. You saw one at the top of the lesson.
A structured binding
auto [name1, name2, ...] = something_with_parts;
auto& [name1, name2, ...] = something_with_parts;
auto [ ... ]makes a copy and names its parts, in order.auto& [ ... ]names the parts of the original, so writing to a name changes it (lesson 04's rule again).- There must be exactly one name per part.
#include <iostream>
#include <string>
#include <tuple>
#include <utility>
int main()
{
std::pair<std::string, int> city = {"Oslo", -3};
std::tuple<std::string, int, double> book = {"Dune", 1965, 4.5};
int point[2] = {3, 4};
auto [place, temp] = city;
auto [title, year, rating] = book;
auto& [x, y] = point;
std::cout << place << ' ' << temp << '\n';
std::cout << title << ' ' << year << ' ' << rating << '\n';
x = 10;
std::cout << point[0] << ' ' << point[1] << '\n';
return 0;
}
Oslo -3
Dune 1965 4.5
10 4
The auto& binding on the array named its real boxes, so x = 10 changed point[0]. Structured bindings shine in a range-for over pairs: for (const auto& [name, score] : board) reads far better than p.first and p.second. So this track writes a structured binding wherever the parts deserve names, which is almost always.
A pair for a moment, a struct for a thing
A pair is quick, and that is its danger. p.first and p.second say nothing about what they hold, and a program full of them reads like a puzzle. Amara's rule: a pair for a moment, a struct for a thing.
A pair is right when two values meet briefly: a function returning two answers, a sort key, an element of a map. A C struct with named fields is right when the values are one thing your program keeps and talks about. A parcel has a weight and a price, and parcel.weight says so.
#include <iostream>
#include <string>
#include <utility>
struct Parcel {
std::string to;
int weight;
int price;
};
std::pair<int, int> splitMinutes(int total)
{
return {total / 60, total % 60};
}
int main()
{
Parcel p = {"Kenji", 3, 120};
std::cout << p.to << ": " << p.weight << " kg, " << p.price << '\n';
auto [hours, minutes] = splitMinutes(135);
std::cout << "delivery in " << hours << " h " << minutes << " min\n";
return 0;
}
Kenji: 3 kg, 120
delivery in 2 h 15 min
The parcel is a thing, kept and printed, so it gets named fields. The hours and minutes exist for one line, so a pair is fine, and the structured binding names them at once. So reach for a pair when the two values are a moment's answer, and for a struct when they are a thing with a name.
std::tie and std::ignore
A structured binding always makes new names. Sometimes the variables already exist. std::tie, from <tuple>, makes a tuple of references to existing variables, so assigning a tuple to it fills them. std::ignore stands in for a part you do not want.
#include <iostream>
#include <string>
#include <tuple>
int main()
{
std::tuple<std::string, int, double> book = {"Dune", 1965, 4.5};
std::string title;
double rating = 0;
std::tie(title, std::ignore, rating) = book;
std::cout << title << ' ' << rating << '\n';
return 0;
}
Dune 4.5
You will mostly read std::tie in other people's code, often in a comparison such as std::tie(a.x, a.y) < std::tie(b.x, b.y), which compares two structs field by field the way pairs compare. So std::tie fills variables you already have, and std::ignore skips a part.
David compares two cities' noon temperatures. Each city is a pair, and the answer is a whole pair too.
#include <iostream>
#include <string>
#include <utility>
int main()
{
std::pair<std::string, int> a = {"Lima", 19};
std::pair<std::string, int> b = {"Perth", 24};
std::pair<std::string, int> hotter = a;
if (b.second > a.second) {
hotter = b;
}
std::cout << hotter.first << " is hotter at " << hotter.second << '\n';
return 0;
}
Perth is hotter at 24
The comparison looks at .second only, because the temperature decides here, not the name. Copying a pair copies both parts at once.
Alice wants a summary of a list of sales: how many, the total and the average. A tuple carries all three back, and a structured binding names them.
#include <iostream>
#include <tuple>
std::tuple<int, long long, double> summary(const int sales[], int n)
{
long long total = 0;
for (int i = 0; i < n; i++) {
total += sales[i];
}
return {n, total, (double)total / n};
}
int main()
{
int sales[5] = {120, 80, 45, 300, 55};
auto [count, total, average] = summary(sales, 5);
std::cout << count << " sales, total " << total << ", average " << average << '\n';
return 0;
}
5 sales, total 600, average 120
The return type names the three types in order, and return {n, total, ...}; fills them in that order. The (double) cast keeps the average's fraction, as in C.
Maria plans a market. Each stall has a name and a position on a grid, and she wants the stall nearest the gate at (0, 0). If two stalls are equally near, she wants the one whose name comes first in the dictionary. A pair of (distance, name) holds that whole rule, because pairs compare distance first and name second.
#include <algorithm>
#include <iostream>
#include <string>
#include <utility>
int main()
{
int n = 0;
std::cin >> n;
std::pair<int, std::string> best = {0, ""};
for (int i = 0; i < n; i++) {
std::string name;
int x = 0;
int y = 0;
std::cin >> name >> x >> y;
std::pair<int, std::string> here = {x * x + y * y, name};
if (i == 0) {
best = here;
} else {
best = std::min(best, here);
}
}
auto [dist2, name] = best;
std::cout << name << " (squared distance " << dist2 << ")\n";
return 0;
}
fruit (squared distance 25)
That output is for the input 4, then tea 6 1, spice 5 0, fruit 3 4 and bread -2 6, one stall per line. Spice and fruit are both 25 away, squared, so the names decide, and "fruit" comes before "spice". Comparing squared distances avoids square roots and their rounding. std::min on two pairs is lesson 05's template, filled in with a pair type.
Where this is used
- Every
std::map. A map's elements are pairs ofconst Keyand value, so every loop over a map reads.firstand.second, or a structured binding (Module 9). - Answers with a yes or no.
std::map::insertreturns a pair: where the element is, and aboolsaying whether it was new (Module 9). - Graphs in contests. An edge list is a
std::vector<std::pair<int, int>>of joined nodes, and Dijkstra's queue holds (distance, node) pairs, sorted by distance first (Module 16). - Two results from one algorithm.
std::minmax_elementfinds the smallest and largest in one pass and returns them as a pair (Module 14).
Common mistakes
1. Brackets after .first.
std::pair<int, int> p = {3, 4};
std::cout << p.first() << '\n';
An error at every command line: error: expression cannot be used as a function. .first is a variable, not a function, so it takes no brackets. You will add them because most things after a dot in the STL are functions, such as .size().
2. Printing a whole pair.
std::cout << p << '\n';
An error at every command line, in over 200 lines on Compiler Explorer at the Playground's flags. The first line is the one to read: error: no match for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream<char>'} and 'std::pair<int, int>'). std::cout does not know how to print a pair, so print the parts: p.first << ' ' << p.second.
3. A tuple part that does not exist.
std::tuple<std::string, int, double> t = {"Zara", 3, 2.5};
std::cout << std::get<3>(t) << '\n';
An error at every command line, raised inside the library: error: static assertion failed: tuple index must be in range, with the note the comparison reduces to '(3 < 3)'. Three parts are numbered 0, 1 and 2, as array indexes are. Bob's off-by-one, caught before the program runs.
4. The wrong number of names in a structured binding.
auto [name, count] = t;
An error at every command line: error: only 2 names provided for structured binding, with the note that the tuple decomposes into 3 elements. Give one name per part, even for a part you will not use.
Amara marks a class test and writes each student's name and mark as she goes. The best mark wins a book. If two students share the best mark, the book goes to the one whose paper she marked first. Keep the winner so far in std::pair<std::string, int> best, as the starter does.
Input. The first line holds n. Each of the next n lines holds a name and a mark.
Output. One line: the winner's name and mark, separated by one space.
Constraints. 1 <= n <= 10000. A name is 1 to 20 lowercase letters. A mark is an integer from 0 to 100.
Sample. Input 4, then alice 82, bob 91, zara 91 and kenji 75, gives bob 91.
#include <iostream>
#include <string>
#include <utility>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n = 0;
std::cin >> n;
std::pair<std::string, int> best;
// Read n lines, each a name and a mark. Keep the best one in best:
// the highest mark, and on a tie the one that came first.
// Then print best's name and mark, separated by one space.
return 0;
}
Graded as best-mark. The hidden tests include n = 1 and a class where every mark is 0. One also has a tie for the best mark that is not on the first line. Zara would run the all-zero class first: think about what best holds before the first read.
Bob shares sweets among friends. Write std::pair<int, int> share(int sweets, int friends), which returns how many each friend gets and how many are left over, and call it on every input line.
Input. The first line holds t. Each of the next t lines holds two integers, the sweets and the friends.
Output. t lines, each each E, left L.
Constraints. 1 <= t <= 100. 0 <= sweets <= 1000000000 and 1 <= friends <= 1000.
Sample. Input 2, then 17 5 and 9 3, gives each 3, left 2 and each 3, left 0.
#include <iostream>
#include <utility>
// Return {sweets each friend gets, sweets left over}.
std::pair<int, int> share(int sweets, int friends)
{
return {0, 0}; // replace this line
}
int main()
{
int t = 0;
std::cin >> t;
for (int i = 0; i < t; i++) {
int sweets = 0;
int friends = 0;
std::cin >> sweets >> friends;
// Call share, name the two answers with a structured binding,
// and print them as: each E, left L
}
return 0;
}
Not graded on its own. Zara would try 0 sweets, and a friend count larger than the sweets.
Run in CompilerAlice runs a book swap. Every book has a shelf number and a slot number on that shelf. She wants the list in the order a person walks past them: shelf by shelf, and slot by slot inside a shelf. The starter reads the pairs into a std::vector<std::pair<int, int>>, an array that knows its own size.
Input. The first line holds n. Each of the next n lines holds a shelf and a slot.
Output. n lines, each a shelf and a slot separated by one space, sorted by shelf and, on the same shelf, by slot. Equal pairs are printed as many times as they appear.
Constraints. 1 <= n <= 40000. Each integer is between -1000000000 and 1000000000.
Sample. Input 4, then 3 1, 1 5, 3 0 and 1 2, gives 1 2, 1 5, 3 0 and 3 1 on four lines.
#include <algorithm>
#include <iostream>
#include <utility>
#include <vector>
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n = 0;
std::cin >> n;
// A vector is an array that knows its own size (Module 2 teaches it).
std::vector<std::pair<int, int>> v(n);
for (auto& p : v) {
std::cin >> p.first >> p.second;
}
// Sort v by first, and by second when the firsts are equal.
// Then print one pair per line: first, one space, second.
return 0;
}
Graded as sort-pairs. The hidden tests include n = 1, six identical pairs, a shelf whose slots arrive in reverse, and 40000 pairs at the limits.
David checks the temperature log of a cold store several times a day. Each check is a short list of readings, and the report needs the coldest and the warmest of each list. The starter declares std::pair<int, int> minMax(const int a[], int k). Make it return the smallest and the largest as one pair, and print both from main.
Input. The first line holds t. Each of the next t lines starts with k, followed by k integers.
Output. t lines, each the smallest and the largest of its list, separated by one space.
Constraints. 1 <= t <= 1000 and 1 <= k <= 1000. All the lists together hold at most 80000 integers. Each integer is between -1000000000 and 1000000000.
Sample. Input 3, then 3 4 -2 7, 1 5 and 4 2 2 2 2, gives -2 7, 5 5 and 2 2 on three lines.
#include <iostream>
#include <utility>
const int MAX_K = 1000;
// Return the smallest and the largest of a[0] to a[k - 1], in that order.
std::pair<int, int> minMax(const int a[], int k)
{
return {0, 0}; // replace this line
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 0;
std::cin >> t;
int a[MAX_K];
for (int i = 0; i < t; i++) {
int k = 0;
std::cin >> k;
for (int j = 0; j < k; j++) {
std::cin >> a[j];
}
// Call minMax and print the smallest and the largest,
// separated by one space.
}
return 0;
}
Graded as min-max-pair. The hidden tests include lists of one reading, lists of only negative readings and lists of only large positive ones. One has 80 lists of 1000 at the limits. A smallest or largest that starts at 0 fails two of those.
Common doubts
Is a pair slower than two separate variables?
No. A pair is a plain struct with two fields, laid out in memory like any C struct. At
-O2the compiler treats its parts like two variables.When should I use a tuple rather than a struct?
Rarely, and briefly. A tuple's parts are numbered, so
std::get<2>tells a reader nothing. Return a tuple from a small function and name its parts at once with a structured binding; for anything you keep, write a struct.Are the names in a structured binding copies?
With
auto [a, b], the whole pair is copied once and the names refer to the copy's parts. Withauto& [a, b]orconst auto& [a, b], the names refer to the original, the same rule as lesson 04.Can a pair hold another pair?
Yes:
std::pair<std::string, std::pair<int, int>>is a name with a position, read asp.second.first. It works, and it is exactly the puzzle code the pair-or-struct rule warns about.
Key takeaways
- A
std::pairholds two values under one name; its parts are.firstand.second, with no brackets. - Pairs and tuples compare lexicographically: the first part decides, and the next part only breaks a tie.
- A
std::tupleholds any number of values, numbered from 0 and read withstd::get<N>. auto [a, b] = p;names every part at once, andauto&names the original's parts.- A pair for a moment, such as a function's two answers; a struct with named fields for a thing.
Next, lesson 07 explains the std:: in front of every name in this module, and which header holds each part of the STL.
End of lesson 6
Mark it done, and your progress moves with you.
Next: std::, Namespaces and the Headers You Will Actually Include