Kodewick
Lesson 14 of 30Contents

pair, struct and enum class

Lesson 14 of 30 · Part 3 · Containers · about 15 minutes

A product has a name, a count and a price; a student has a name and a score. When a few things always go together, keep them together. The simplest way is std::pair: exactly two things, read with .first and .second, just like each pair in last lesson's map. It needs #include <utility>.

main.cpp
1#include <iostream>2#include <string>3#include <utility>4 5int main() {6    std::pair<std::string, int> best{"Ana", 95};7    std::cout << best.first << " scored " << best.second << '\n';8    best.second = 98;9    std::pair<std::string, int> next = std::make_pair("Ben", 90);10    std::cout << best.second << " " << next.first << '\n';11    return 0;12}
Output
Ana scored 95
98 Ben

std::pair<std::string, int>: the first thing is a string, the second an int. std::make_pair("Ben", 90) makes a pair from the two values in the parentheses without writing the types in angle brackets; the function below uses it to return its result.

pair: returning two values at once

A function can return only one value. To return two, return a pair:

main.cpp
1#include <iostream>2#include <utility>3#include <vector>4 5std::pair<int, int> low_high(const std::vector<int>& temps) {6    int low = temps[0];7    int high = temps[0];8    for (int t : temps) {9        if (t < low) {10            low = t;11        }12        if (t > high) {13            high = t;14        }15    }16    return std::make_pair(low, high);17}18 19int main() {20    std::vector<int> week{18, 23, 16, 21, 19};21    std::pair<int, int> r = low_high(week);22    std::cout << "Low " << r.first << ", high " << r.second << '\n';23    return 0;24}
Output
Low 16, high 23

Two pairs can be compared too: .first first, and if those are equal, .second. So a vector of pairs can go straight into sort:

Guess first

After sorting, which pair is v[1]?

1#include <algorithm>2#include <iostream>3#include <utility>4#include <vector>5 6int main() {7    std::vector<std::pair<int, int>> v{{2, 9}, {1, 5}, {2, 3}};8    std::sort(v.begin(), v.end());9    std::cout << v[1].first << " " << v[1].second << '\n';10    return 0;11}

struct: a type of your own

A pair holds only two things, and their names, first and second, say nothing about what they are. For three or more, or for clear names, define your own type with struct. The variables inside are its members, and you reach them with .:

main.cpp
1#include <iostream>2#include <string>3 4struct Item {5    std::string name;6    int count;7    double price;8};9 10int main() {11    Item pen{"pen", 3, 2.5};12    pen.count += 2;13    std::cout << pen.name << ": " << pen.count << " of them, ";14    std::cout << "$" << pen.count * pen.price << " in all\n";15    return 0;16}
Output
pen: 5 of them, $12.5 in all

struct Item { ... }; defines a new type, Item, and from then on you can declare variables with it just like with int. Item pen{"pen", 3, 2.5} gives the values in the order of the members. Press Step and see pen drawn with its three fields.

The closing curly brace of a struct definition needs a semicolon. It is the one people forget most:

main.cpp
1#include <iostream>2#include <string>3 4struct Item {5    std::string name;6    int count;7}8 9int main() {10    Item cup{"cup", 4};11    std::cout << cup.name << '\n';12    return 0;13}
Output
main.cpp:7:2: error: expected ';' after struct definition
    7 | }
      |  ^
      |  ;

g++ points at the } on line 7 and says expected ';' after struct definition.

A vector of structs

Put a struct in a vector and you have a table: each element is a row and each member a column. Pass one to a function like any other big thing, as const Item&:

main.cpp
1#include <iostream>2#include <string>3#include <vector>4 5struct Item {6    std::string name;7    int count;8    double price;9};10 11double value(const Item& item) {12    return item.count * item.price;13}14 15int main() {16    std::vector<Item> stock{{"pen", 5, 2.5}, {"notebook", 3, 8.0}, {"eraser", 10, 1.5}};17    double total = 0;18    for (const auto& item : stock) {19        std::cout << item.name << ": $" << value(item) << '\n';20        total += value(item);21    }22    std::cout << "Total: $" << total << '\n';23    return 0;24}
Output
pen: $12.5
notebook: $24
eraser: $15
Total: $51.5

Each {...} inside the curly braces is one Item.

Sorting by a member

std::sort doesn't know which of two Items is smaller. You tell it with a function: two parameters, returning true when the first should come before the second. Hand the function's name to sort as the third argument:

main.cpp
1#include <algorithm>2#include <iostream>3#include <string>4#include <vector>5 6struct Student {7    std::string name;8    int score;9};10 11bool higher_score(const Student& a, const Student& b) {12    return a.score > b.score;13}14 15int main() {16    std::vector<Student> cls{{"Ana", 82}, {"Ben", 95}, {"Cara", 77}};17    std::sort(cls.begin(), cls.end(), higher_score);18    for (const auto& s : cls) {19        std::cout << s.name << " " << s.score << '\n';20    }21    return 0;22}
Output
Ben 95
Ana 82
Cara 77

There are no parentheses after higher_score: you aren't calling it here, you are handing it to sort, which calls it whenever it needs to compare. With > the higher scores come first; with <, the lower ones. Lesson 22 covers lambdas, which let you write the comparison right inside the call to sort.

enum class: names for choices

Some values can only be one of a few choices: a size is small, medium or large; an order is unpaid, shipped or delivered. Use 0, 1 and 2 and in a few days you'll forget what 2 meant. enum class gives each choice a name:

main.cpp
1#include <iostream>2 3enum class Size { Small, Medium, Large };4 5int main() {6    Size s = Size::Large;7    int code = s;8    std::cout << code << '\n';9    return 0;10}
Output
main.cpp: In function 'int main()':
main.cpp:7:16: error: cannot convert 'Size' to 'int' in initialization
    7 |     int code = s;
      |                ^
      |                |
      |                Size

enum class Size defines a type whose only values are Size::Small, Size::Medium and Size::Large. They are not ints, so int code = s; doesn't compile: cannot convert 'Size' to 'int'. That is on purpose: a size shouldn't be added and subtracted like a number by accident.

When you really want the number behind it, use static_cast<int>:

Guess first

What does this show?

1#include <iostream>2 3enum class Size { Small, Medium, Large };4 5int main() {6    Size s = Size::Large;7    std::cout << static_cast<int>(s) << '\n';8    return 0;9}

switch with enum class

enum class and switch go well together: one case per choice, and it reads like a list of what each one means:

main.cpp
1#include <iostream>2#include <string>3 4enum class Status { Unpaid, Shipped, Delivered };5 6std::string describe(Status s) {7    switch (s) {8        case Status::Unpaid:9            return "waiting for payment";10        case Status::Shipped:11            return "on its way";12        case Status::Delivered:13            return "delivered";14    }15    return "unknown";16}17 18int main() {19    Status order = Status::Unpaid;20    std::cout << describe(order) << '\n';21    order = Status::Shipped;22    std::cout << describe(order) << '\n';23    return 0;24}
Output
waiting for payment
on its way

Each case returns right away, so no break is needed. The return "unknown"; after the switch makes sure the function always returns something.

Tip: Compile with g++ -Wall and g++ warns you when a switch leaves out one of the choices. Add a new choice to Status later, and the compiler finds every place you need to update.

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

The priciest one

Finish most_expensive: go through items and return the one with the highest price. main is already written; it asks about two lists.

It should print
Priciest: headphones 199
Priciest: pan 268
exercise1.cpp
1#include <iostream>2#include <string>3#include <vector>4 5struct Item {6    std::string name;7    int price;8};9 10Item most_expensive(const std::vector<Item>& items) {11    Item best = items[0];12 13    return best;14}15 16int main() {17    std::vector<Item> desk{{"mouse", 89}, {"headphones", 199}, {"keyboard", 159}};18    std::vector<Item> kitchen{{"kettle", 129}, {"knife", 99}, {"pan", 268}};19    Item a = most_expensive(desk);20    std::cout << "Priciest: " << a.name << " " << a.price << '\n';21    Item b = most_expensive(kitchen);22    std::cout << "Priciest: " << b.name << " " << b.price << '\n';23    return 0;24}
Output
Press Run or Ctrl+Enter
Exercise 2

Line up by height

Write a comparison function that sorts kids from shortest to tallest, then show each name and height on its own line.

It should print
Mia 121
Leo 128
Sam 134
exercise2.cpp
1#include <algorithm>2#include <iostream>3#include <string>4#include <vector>5 6struct Kid {7    std::string name;8    int height;9};10 11int main() {12    std::vector<Kid> kids{{"Leo", 128}, {"Mia", 121}, {"Sam", 134}};13 14    return 0;15}
Output
Press Run or Ctrl+Enter
Exercise 3

Fix it: a size is not a number

This program doesn't compile. Fix it so it shows Size code: 1.

It should print
Size code: 1
exercise3.cpp
1#include <iostream>2 3enum class Size { Small, Medium, Large };4 5int main() {6    Size s = Size::Medium;7    int code = s;8    std::cout << "Size code: " << code << '\n';9    return 0;10}
Output
Press Run or Ctrl+Enter

What you learned

  • std::pair<A, B> holds two things, read with .first and .second; std::make_pair works out the types; a function can return a pair.
  • pairs compare by .first, then .second, so they sort as they are.
  • struct defines a type of your own; reach its members with .. The closing curly brace needs a semicolon.
  • A std::vector<Item> is a table; pass an Item to functions as const Item&.
  • To sort by a member, write a comparison function that returns bool and give it to std::sort as the third argument.
  • enum class names choices (Size::Large); it isn't an int without static_cast<int>, and it reads best with switch.