Show Lecture.PairAndTuple as a slide show.
CS253 Pair And Tuple
Inclusion
pair is defined via:
#include <utility>
tuple is defined via:
#include <tuple>
- A pair:
- is a templated class
- has two public data members:
.first
and .second
.
pair<string, int> who("Jack", 253);
cout << who.first << " teaches " << who.second;
Jack teaches 253
- pair does not have
operator[]
. What type(s) would it return,
string or int? who[0]
and who[1]
can’t have different
return types!
pair<string, int> who("Jack", 253);
cout << who[0]; // 🦡
c.cc:2: error: no match for ‘operator[]’ in ‘who[0]’ (operand types are
‘std::pair<std::__cxx11::basic_string<char>, int>’ and ‘int’)
pair definition
- pair is not much more than this:
template<typename F, typename S>
struct pair {
F first;
S second;
};
- I consider it to be a cheap struct for very local use.
- For wider use, make a struct with
mnemonic names.
- Comparison (
==
, !=
, <
, <=
, >
, >=
)
to other pairs works.
- If the
.first
elements are unequal, we’re done.
- Otherwise, compare the
.second
elements.
- This is called lexicographic comparison.
- A
map<
key,
value >
is pretty much just
a set<pair<const
key,
value >>
,
except that it’s sorted only by the key.
*
map::begin() returns a reference to the first item
in the map, which is a
pair<const
key,
value >
.
- The key is const because changing the key would put
the map out of order.
- The value is not const, and can be changed:
map<int,string> m = {{1893, "Pepsi"}, {1886, "Coca-Cola"}};
m.begin()->second = "Coke";
for (auto p : m)
cout << p.first << ": " << p.second << '\n';
1886: Coke
1893: Pepsi
pair comparison
vector<pair<string, string>> ff = {
{"Storm", "Johnny"},
{"Storm", "Sue"},
{"Grimm", "Ben"},
{"Richards", "Reed"},
};
sort(ff.begin(), ff.end());
for (auto &p : ff)
cout << p.second << ' ' << p.first << '\n';
Ben Grimm
Reed Richards
Johnny Storm
Sue Storm
- For sort() to work, the pairs had to be less-than comparable.
- Primary sort key:
.first
- Secondary sort key:
.second
- Did you notice the valid trailing comma?
multimap<string, string> ff = {
{"Storm", "Johnny"},
{"Storm", "Sue"},
{"Grimm", "Ben"},
{"Richards", "Reed"},
};
for (auto &p : ff)
cout << p.second << ' ' << p.first << '\n';
Ben Grimm
Reed Richards
Johnny Storm
Sue Storm
- I needed a multimap because there are two Storm siblings.
- Two-level sorting: first by
.first
, second by .second
.
- A tuple:
- rhymes with “scruple”, e.g., “two-pull”
- a generalization of pair, with as many fields as you like
- does not have
.first
, .second
, .third
, etc.
- has non-methods
get<0>(
tuple)
,
get<1>(
tuple)
, get<2>(
tuple)
, etc.
- These are compile-time indices.
[0]
, [1]
, … do not work.
- has non-method
get<
type>(
tuple)
- has all comparison methods
(
<
, >
, <=
, >=
, ==
, !=
),
lexicographically defined
using person = tuple<string, float, int, bool>;
person orange("DJT", 75, 239, false);
cout << boolalpha
<< "Name: " << get<0>(orange) << "\n"
<< "Height: " << get<float>(orange) << "″\n"
<< "Weight: " << get<2>(orange) << "#\n"
<< "Popular: " << get<bool>(orange) << "\n";
Name: DJT
Height: 75″
Weight: 239#
Popular: false
Output
You can’t just <<
a pair or a tuple.
What would go between the elements?
pair<int,int> p(3,4);
cout << p; // 🦡
c.cc:2: error: no match for ‘operator<<’ in ‘std::cout << p’ (operand
types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and
‘std::pair<int, int>’)
Instead, display both .first
and .second
,
with something inbetween:
pair<int,int> p(1,2);
cout << p.first << '/' << p.second;
1/2
get
with pair
- It’s clear that pair came first, and was generalized to tuple.
- At that point, they were stuck with pair. Compatibility!
- Hence, some interfaces that accept a tuple also accept a pair.
pair<string,float> p("π", 3.14159);
cout << p.first << '\n'
<< get<string>(p) << '\n'
<< get<0>(p) << '\n'
<< p.second << '\n'
<< get<float>(p) << '\n'
<< get<1>(p) << '\n';
π
π
π
3.14159
3.14159
3.14159
Structured binding
You can assign several variables at once (from a pair, tuple,
C array, or just a struct). This is called structured binding.
It was formerly accomplished with tie():
Extracting values from a pair:
pair p("pi", 3.14159);
auto [s, f] = p;
cout << s << ' ' << f << '\n';
pi 3.14159
References to values in a tuple:
tuple t(1.2, 44, 'x');
auto &[d, i, c] = t;
cout << d << ' ' << i << ' ' << c << '\n';
1.2 44 x
More structured binding
Const references to values in a C array:
int a[] = {11,22};
const auto & [x, y] = a;
cout << x << ' ' << y << '\n';
11 22
Copying values from a struct:
struct transaction {
string currency;
const char *symbol;
double amount;
};
struct transaction trans{"Euro", "€", 1.23};
auto [cur, sym, amt] = trans;
cout << cur << '/' << sym << '/' << amt << '\n';
Euro/€/1.23
Uses for structured binding
- Returning several values (via a tuple) from a function,
rather than using writable reference arguments.
- Breaking apart the pair returned by set::insert().
- Using a for-each loop to iterate over a map,
which returns a pair for each element.
map without structured binding
map<int, string> numbers = {
{ 3, "three" },
{ 0, "zero" },
{ 2, "two" },
{ 1, "one" },
};
numbers[5] = "five";
numbers[4] = "four";
for (auto p : numbers)
cout << p.first << " is " << p.second << '\n';
0 is zero
1 is one
2 is two
3 is three
4 is four
5 is five
map with structured binding
map<int, string> numbers = {
{ 3, "three" },
{ 0, "zero" },
{ 2, "two" },
{ 1, "one" },
};
numbers[5] = "five";
numbers[4] = "four";
for (auto [left,right] : numbers)
cout << left << " is " << right << '\n';
0 is zero
1 is one
2 is two
3 is three
4 is four
5 is five
- Much prettier!
- We’re still copying each string as we iterate.
A wee bit faster
map<int, string> numbers = {
{ 3, "three" },
{ 0, "zero" },
{ 2, "two" },
{ 1, "one" },
};
numbers[5] = "five";
numbers[4] = "four";
for (const auto & [left,right] : numbers)
cout << left << " is " << right << '\n';
0 is zero
1 is one
2 is two
3 is three
4 is four
5 is five
- Using a reference (
auto &
) saves copying those strings.
It might matter, it might not.
- const stresses that we’re not changing the map.
- Pedantic? Yes!
- Worth it? Who can say?
CTAD
The C++17 feature
Class Template Argument Deduction, or CTAD,
used in the structured binding examples, allows the omission of the type
in some cases when a container is being initialized.
pair<int, string> me(253, "Jack"); // explicit types
pair him(314, "Dave"); // implicit types, like auto
tuple who(165, "Russ"); // implicit types, like auto
cout << me.first << " taught by " << me.second << '\n'
<< him.first << " taught by " << him.second << '\n'
<< get<int>(who) << " taught by " << get<1>(who) << '\n';
253 taught by Jack
314 taught by Dave
165 taught by Russ
This makes variable initialization such as pair him
a lot simpler.
What type is him.first
?
It’s the same type as 314
, which is int.
What type is him.second
?
Same type as "Dave"
, which is const char *
,
not std::string.