COMP6771
Advanced C++ Programming
Week 2
C++ Standard Library
What's a library?
Why do we want to use libraries?
unsigned int what_am_i(std::vector<int> const& v, int const x) {
for (auto i = 0U; i <= v.size(); ++i) {
if (v[i] == x) {
return i;
}
}
return v.size();
}
Why do we want to use libraries?
unsigned int find(std::vector<int> const& v, int const x) {
for (auto i = 0U; i <= v.size(); ++i) {
if (v[i] == x) {
return i;
}
}
return v.size();
}
Why do we want to use libraries?
"Every line of code you don't write is bug-free!"
Code in a popular library is often:
- Well-documented
- Well-tested
- Well-reviewed
- Has lots of feedback
Libraries COMP6771 uses
We use the following extremely popular libraries
- C++ standard library
- Catch2 (test framework)
You are not expected to learn everything in these libraries.
We will instead cherry-pick certain useful components from each.
Catch2 recap
#include <catch2/catch.hpp>
TEST_CASE("empty vectors") { // Opens up a context for testing
}
Catch2 recap
#include <catch2/catch.hpp>
TEST_CASE("empty vectors") { // Opens up a context for testing
auto v = std::vector<int>();
REQUIRE(v.empty()); // Aborts the test case (not the program) on failure.
}
Catch2 recap
#include <catch2/catch.hpp>
TEST_CASE("empty vectors") { // Opens up a context for testing
auto v = std::vector<int>();
REQUIRE(v.empty()); // Aborts the test case (not the program) on failure.
SECTION("check we can insert elements to the back") { // Opens a sub-context, where everything in the outer
// scope run for *each* SECTION.
}
}
Catch2 recap
#include <catch2/catch.hpp>
TEST_CASE("empty vectors") { // Opens up a context for testing
auto v = std::vector<int>();
REQUIRE(v.empty()); // Aborts the test case (not the program) on failure.
SECTION("check we can insert elements to the back") { // Opens a sub-context, where everything in the outer
v.push_back(5); // scope run for *each* SECTION.
REQUIRE(v.size() == 1);
CHECK(v[0] == 1); // Gives a meaningful message on failure, but doesn't abort.
CHECK(v.back() == v[0]);
}
}
Catch2 recap
#include <catch2/catch.hpp>
TEST_CASE("empty vectors") { // Opens up a context for testing
auto v = std::vector<int>();
REQUIRE(v.empty()); // Aborts the test case (not the program) on failure.
SECTION("check we can insert elements to the back") { // Opens a sub-context, where everything in the outer
v.push_back(5); // scope run for *each* SECTION.
REQUIRE(v.size() == 1);
CHECK(v[0] == 1); // Gives a meaningful message on failure, but doesn't abort.
CHECK(v.back() == v[0]);
SECTION("check we can insert elements to the front") {
}
SECTION("check we can remove elements from the back") {
}
}
}
Catch2 recap
#include <catch2/catch.hpp>
TEST_CASE("empty vectors") { // Opens up a context for testing
auto v = std::vector<int>();
REQUIRE(v.empty()); // Aborts the test case (not the program) on failure.
SECTION("check we can insert elements to the back") { // Opens a sub-context, where everything in the outer
v.push_back(5); // scope run for *each* SECTION.
REQUIRE(v.size() == 1);
CHECK(v[0] == 1); // Gives a meaningful message on failure, but doesn't abort.
CHECK(v.back() == v[0]);
SECTION("check we can insert elements to the front") {
auto const result = v.insert(v.begin(), -1);
REQUIRE(v.size() == 2);
CHECK(result == v.begin());
CHECK(v[0] == -1);
CHECK(v.front() == v[0]);
CHECK(v[1] == 0);
CHECK(v.back() == v[1]);
}
SECTION("check we can remove elements from the back") {
}
}
}
Catch2 recap
#include <catch2/catch.hpp>
TEST_CASE("empty vectors") { // Opens up a context for testing
auto v = std::vector<int>();
REQUIRE(v.empty()); // Aborts the test case (not the program) on failure.
SECTION("check we can insert elements to the back") { // Opens a sub-context, where everything in the outer
v.push_back(5); // scope run for *each* SECTION.
REQUIRE(v.size() == 1);
CHECK(v[0] == 1); // Gives a meaningful message on failure, but doesn't abort.
CHECK(v.back() == v[0]);
SECTION("check we can insert elements to the front") {
auto const result = v.insert(v.begin(), -1);
REQUIRE(v.size() == 2);
CHECK(result == v.begin());
CHECK(v[0] == -1);
CHECK(v.front() == v[0]);
CHECK(v[1] == 0);
CHECK(v.back() == v[1]);
}
SECTION("check we can remove elements from the back") {
v.pop_back();
CHECK(v.empty()); // remember that each section inherits an independent context from its parent scope
}
}
}
Output streams
// #include <iostream>
// Character output
std::cout << "The meaning of life is " << 42 << '\n'
<< "pi = " << 3.14159265 << '\n';
Output streams
// #include <fstream> // for std::ofstream
// #include <iostream> // for std::cerr
if (auto file_out = std::ofstream("file.txt")) {
file_out << "The meaning of life is " << 42 << '\n'
<< "pi = " << 3.14159265 << '\n';
}
else {
std::cerr << "Unable to open 'file.txt'";
}
Checks the file successfully opened.
Automagically closes the file.
Output streams
// #include <sstream>
// #include <string>
auto serialise = std::ostringstream();
serialise << "The meaning of life is " << 42 << '\n'
<< "pi = " << 3.14159265 << '\n';
auto const output = serialise.str();
Input streams
// #include <iostream>
// Character input
if (auto i = 0; std::cin >> i) {
std::cout << "You input " << i << '\n';
}
else if (std::cin.eof()) {
std::cerr << "End of file identified.\n";
}
else if (std::cin.bad()) {
std::cerr << "Something non-recoverable happened.\n";
}
else {
std::cerr << "Recoveraboe error: did you try inputting a non-integer?\n";
}
Input streams
// #include <iostream>
// #include <fstream>
if (auto file_in = std::ifstream("file.txt")) {
if (auto i = 0; file_in >> i) {
std::cout << "You input " << i << '\n';
}
else if (file_in.eof()) {
std::cerr << "End of file identified.\n";
}
else if (file_in.bad()) {
std::cerr << "Something non-recoverable happened (e.g. disk disconnected).\n";
}
else {
std::cerr << "Recoverable error: did you try inputting a non-integer?";
}
}
else {
std::cerr << "Unable to open 'file.txt'\n"
}
Input streams
// #include <iostream>
// #include <sstream>
auto deserialise = std::istringstream("10");
if (auto i = 0; deserialise >> i) {
std::cout << "You input " << i << '\n';
}
else if (deserialise.bad()) {
std::cerr << "Something non-recoverable happened.\n";
}
else if (not deserialise.eof()) {
std::cerr << "Recoverable error: did you try inputting a non-integer?";
}
Themes
Containers
Algorithms
Ranges
Iterators
Containers
Abstractions of common data structures.
Are objects that you can "put" other objects "into".
Container operations vary in time and space complexity.
Performance has a basis in physics (see Week 10).
std::vector is always the default container (see Week 10).
Sequence containers
Organises a finite set of objects into a strict linear arrangement.
Dynamically-sized array.
Fixed-sized array.
Double-ended queue.
Singly-linked list.
Doubly-linked list.
We will explore these in greater detail in Week 10.
It won't be necessary to use anything other than std::vector
in COMP6771.
Unordered associative containers
Provide fast retrieval of data based on keys. The keys are hashed.
A collection of unique keys.
Associative array that map unique keys to a values.
Associative containers
Provide fast retrieval of data based on keys. The keys are sorted.
A collection of unique keys.
Associative array that map a unique keys to values.
They are mostly interface-compatible with the unordered associative containers.
String processing
// #include <string>
auto const greeting = std::string("hello, world!");
User-defined literals (UDLs)
// #include <string>
using namespace std::string_literals;
auto const greeting = "hello, world"s;
Put using-directives in the smallest scope possible
// #include <string>
int main() {
using namespace std::string_literals;
auto const greeting = "hello, world"s;
}
String concatenation
// #include <string>
auto const greeting = "hello" + "world" + '!';
std::vector
revisited
auto some_ints = std::vector<int>{0, 1, 2, 3, 2, 5};
REQUIRE(some_ints.size() == 6);
// Querying a vector
CHECK(some_ints[0] == 0);
CHECK(some_ints[1] == 1);
CHECK(some_ints[2] == 2);
CHECK(some_ints[3] == 3);
CHECK(some_ints[4] == 2);
CHECK(some_ints[5] == 5);
std::vector grows as we add elements
auto some_ints = std::vector<int>{0, 1, 2, 3, 2, 5};
REQUIRE(some_ints.size() == 6);
some_ints.push_back(42);
REQUIRE(some_ints.size() == 7);
CHECK(some_ints[6] == 42);
std::vector shrinks as we remove elements
auto some_ints = std::vector<int>{0, 1, 2, 3, 2, 5};
REQUIRE(some_ints.size() == 6);
some_ints.pop_back();
REQUIRE(some_ints.size() == 5);
CHECK(some_ints.back() == 2);
std::vector shrinks as we remove elements
auto some_ints = std::vector<int>{0, 1, 2, 3, 2, 5};
REQUIRE(ranges::distance(some_ints) == 6);
some_ints.pop_back();
REQUIRE(ranges::distance(some_ints) == 5);
CHECK(some_ints.back() == 2);
// erases any occurrence of 2
std::erase(some_ints, 2);
REQUIRE(some_ints.size() == 4);
CHECK(some_ints[2] == 3);
std::vector shrinks as we remove elements
auto some_ints = std::vector<int>{0, 1, 2, 3, 2, 5};
REQUIRE(some_ints.size() == 6);
some_ints.clear(); // removes *all* the elements
CHECK(some_ints.empty()); // "does some_ints have elements?"
auto const no_elements = std::vector<int>();
REQUIRE(no_elements.empty());
CHECK(some_elements == no_elements);
I want a vector with five zeroes
auto all_default = std::vector<double>(5);
REQUIRE(all_default.size() == 5);
CHECK(all_default[0] == 0.0);
CHECK(all_default[1] == 0.0);
CHECK(all_default[2] == 0.0);
CHECK(all_default[3] == 0.0);
CHECK(all_default[4] == 0.0);
I want a vector with three identical values
auto const initial_value = std::string("some words go here!");
auto all_same = std::vector<std::string>(3, initial_value);
REQUIRE(all_same.size() == 3);
CHECK(all_same[0] == initial_value);
CHECK(all_same[1] == initial_value);
CHECK(all_same[2] == initial_value);
all_same[1] = "other words";
CHECK(all_same[0] != all_same[1]);
CHECK(all_same.front() == all_same.back());
A card game
enum class colour { red, green, blue, yellow };
enum class value { number, draw_two, draw_four, reverse, skip };
struct card {
colour colour;
value value;
friend bool operator==(card, card) = default;
};
A card game
auto const red_number = card{colour::red, value::number};
auto const blue_number = card{colour::blue, value::number};
auto const green_draw_two = card{colour::green, value::draw_two};
auto const blue_skip = card{colour::blue, value::skip};
auto const yellow_draw_four = card{colour::yellow, value::draw_four};
Stacks
// #include <stack>
auto deck = std::stack<card>();
REQUIRE(deck.empty());
deck.push(red_number);
deck.push(green_draw_two);
deck.push(green_draw_two);
deck.push(yellow_draw_four);
deck.push(blue_number);
REQUIRE(deck.size() == 5);
Removing elements from a stack
// #include <stack>
CHECK(deck.top() == blue_number);
deck.pop();
CHECK(deck.top() == yellow_draw_four);
deck.pop();
// ...
Comparing two stacks
auto const more_cards = deck;
REQUIRE(more_cards == deck);
deck.pop();
CHECK(more_cards != deck);
Queues
// #include <queue>
auto deck = std::queue<card>();
REQUIRE(deck.empty());
deck.push(red_number);
deck.push(green_draw_two);
deck.push(green_draw_two);
deck.push(yellow_draw_four);
deck.push(blue_number);
Removing elements from a queue
// #include <stack>
CHECK(deck.front() == red_number);
deck.pop();
CHECK(deck.front() == green_draw_two);
deck.pop();
// ...
Comparing two queues
auto const more_cards = deck;
REQUIRE(more_cards == deck);
deck.pop();
CHECK(more_cards != deck);
A range is an ordered sequence of elements with a designated start and rule for finishing
std::vector<std::string>{"Hello", "world!"}
std::string("Hello, world!")
ℕ ℤ⁺ ℚ⁺ ℝ⁺
Exercise: how can ℂ be made into a range?
for (auto i = 0; std::cin >> i;) { ... }
Find revisited
unsigned int find(std::vector<int> const& v, int const value) {
auto index = 0U;
for (auto const i : v) {
if (i == value) {
return index;
}
++index;
}
return index;
}
What type should the index become?
// Note: ??? is not a valid C++ symbol
??? find(std::vector<int> const& v, int const value) {
auto index = ???;
for (auto const i : v) {
if (i == value) {
return index;
}
++index;
}
return index;
}
A Java linked list
class Node {
Node next;
public int value;
}
public class LinkedList {
private Node head;
public Node find(final int value) {
Node i = head;
while (i != null) {
if (i.value == value) {
return i;
}
i = i.next;
}
return null;
}
}
Compare the pair
public Node find(final int value) {
Node i = head;
while (i != null) {
if (i.value == value) {
return i;
}
i = i.next;
}
return null;
}
unsigned int find(std::vector<int> const& v, int const value) {
auto index = 0U;
for (auto const i : v) {
if (i == value) {
return index;
}
++index;
}
return index;
}
Could you imagine having to do that for...
...a doubly-linked list?
...a static vector?
...a double-ended queue?
...std::string?
...a skip list?
...a cord?
𝑥 sequence containers
𝑦 sequence algorithms (e.g. find)
×
≈
𝑥𝑦 algorithm implementations
7 sequence containers
110 sequence algorithms (e.g. find)
×
≈
770 algorithm implementations
and we haven't come close to exhausting either group
We need an intermediate representation
We need an intermediate representation
vector
Iterator
Algorithm
Result
Result sink
static vector
string
cord
skip list
linked list
The result may be a one or more iterators, a scalar value, or some combination of both.
𝑥 sequence containers
𝑦 sequence algorithms (e.g. find)
+
≈
𝑥 + 𝑦 total implementations
7 sequence containers
110 sequence algorithms (e.g. find)
+
≈
117 total implementations
and we haven't come close to exhausting either group
Operation
Array-like
Node-based
Iterator
Iterators: our intermediate representation
Iteration type
unsigned int
node*
unspecified
Read element
v[i]
i->value
*i
++i
Successor
i = i->next
++i
Advance fwd
j = i + n < v.size()
? i + n
: v.size();
std::ranges::next(i, s, n)
j = i->successor(n)
Advance back
--i
i = i->prev
--i
Comparison
i < v.size()
i != nullptr
i != s
Predecessor
j = i - n < 0 ? 0 : i - n
std::ranges::prev(i, s, n)
j = i->predecessor(n)
Find revisited (again)
std::vector<int>::iterator
find(std::vector<int> const& v, int const value) {
for (auto i = v.begin(); i != v.end(); ++i) {
if (*i == value) {
return i;
}
}
return v.end();
}
Generating a hand of cards
auto hand = std::vector<card>{
red_number,
blue_number,
green_draw_two,
blue_number,
blue_skip,
yellow_draw_four,
blue_number,
blue_number,
blue_skip,
};
// #include <algorithm>
CHECK(std::ranges::count(hand, red_number) == 1);
CHECK(std::ranges::count(hand, blue_number) == 4);
CHECK(std::ranges::count(hand, blue_skip) == 2);
// #include <algorithm>
auto card_to_play = std::ranges::find(hand, blue_number);
REQUIRE(card_to_play != hand.cend());
CHECK(*card_to_play == blue_number);
What is card_to_play?
Red number
Blue number
Green draw 2
Blue number
Blue skip
Yellow draw 4
Blue number
Blue number
Blue skip
Position: 1
Value: blue_number
Finding a card
// #include <algorithm>
auto const green_draw_four = card{colour::green, value::draw_four};
auto card_to_play = std::ranges::find(hand, green_draw_four);
REQUIRE(card_to_play == hand.cend());
What is card_to_play?
Red number
Blue number
Green draw 2
Blue number
Blue skip
Yellow draw 4
Blue number
Blue number
Blue skip
Position: 10
Value: n/a
// #include <algorithm>
auto card_to_play = std::ranges::find(hand, blue_number);
REQUIRE(card_to_play != hand.cend());
CHECK(*card_to_play == blue_number);
card_to_play = hand.erase(card_to_play);
REQUIRE(card_to_play != hand.cend());
CHECK(*card_to_play == green_draw_two);
What is card_to_play?
Red number
Green draw 2
Blue number
Blue skip
Yellow draw 4
Blue number
Blue number
Blue skip
Position: 1
Value: green_draw_two
Adding elements and iterators
// #include <algorithm>
auto card_to_play = std::ranges::find(hand, blue_number);
REQUIRE(card_to_play != hand.cend());
CHECK(*card_to_play == blue_number);
hand.push_back(green_draw_two);
What is card_to_play?
Red number
Blue number
Green draw 2
Blue number
Blue skip
Yellow draw 4
Blue number
Blue number
Blue skip
Position: 1
Value: blue_number
What is card_to_play?
Red number
Green draw 2
Blue number
Blue skip
Yellow draw 4
Blue number
Blue number
Blue skip
Position: 1
Value: blue_number
Blue number
Green draw 2
What is card_to_play?
// #include <algorithm>
auto card_to_play = std::ranges::find(hand, blue_number);
REQUIRE(card_to_play != hand.cend());
CHECK(*card_to_play == blue_number);
hand.push_back(green_draw_two);
card_to_play = std::ranges::find(hand, blue_number);
REQUIRE(card_to_play != hand.cend());
CHECK(*card_to_play == blue_number);
What is card_to_play?
Red number
Green draw 2
Blue number
Blue skip
Yellow draw 4
Blue number
Blue number
Blue skip
Position: 1
Value: blue_number
Blue number
Green draw 2
// #include <algorithm>
auto card_to_play = std::ranges::adjacent_find(hand, blue_number);
REQUIRE(card_to_play != hand.cend());
CHECK(*card_to_play == blue_number);
Red number
Blue number
Green draw 2
Blue number
Blue skip
Yellow draw 4
Blue number
Blue number
Blue skip
Position: 6
Value: blue_number
How many blue cards are there?
// #include <algorithm>
auto const blue_cards = std::ranges::count_if(hand, [](card const c) {
return c.colour == colour::blue;
});
auto const expected_blue_cards = 6;
CHECK(blue_cards == expected_blue_cards);
Red number
Blue number
Green draw 2
Blue number
Blue skip
Yellow draw 4
Blue number
Blue number
Blue skip
Lambda unary predicate
[](card const c) {
return c.colour == colour::blue;
}
Explicit return type
[](card const c) -> bool {
return c.colour == colour::blue;
}
We'll need this to do a binary search
#include <compare>
enum class colour { red, green, blue, yellow };
enum class value { number, draw_two, draw_four, reverse, skip };
struct card {
colour colour;
value value;
friend bool operator==(card, card) = default;
friend auto operator<=>(card, card) = default;
};
// #include <algorithm>
std::ranges::sort(hand);
REQUIRE(std::ranges::is_sorted(hand));
auto [first, last] = std::ranges::equal_range(hand, blue_number);
REQUIRE(first != last);
CHECK(std::ranges::distance(first, last) == 4);
CHECK(std::ranges::all_of(first, last, [blue_number](card const x) {
return x == blue_number;
}));
Binary search
Red number
Blue number
Green draw 2
Blue number
Blue skip
Yellow draw 4
Blue number
Blue number
Blue skip
Position: 2
Value: blue_skip
Position: 9
Value: n/a
Lambda with value capture
[blue_number](card const x) {
return x == blue_number;
}
Closures
auto const blue_then_yellow = [](card const x, card const y) {
return x.colour == colour::blue and y.colour == colour::yellow;
};
auto const blue_card = std::ranges::adjacent_find(hand, blue_then_yellow);
REQUIRE(blue_card != hand.end());
CHECK(*blue_card == blue_skip);
auto const yellow_card = std::ranges::next(blue_card);
CHECK(*yellow_card == yellow_draw_four);
Let's take a note of how many swaps we do
void note_swaps(std::map<card, int>& cards_swapped,
card const c) {
auto result = cards_swapped.find(c);
if (result == cards_swapped.end()) {
cards_swapped.emplace(c, 1);
return;
}
++result->second;
}
With house rules
// #include <algorithm>
// house rule: two players can swap a card of the same value
// (but for a different colour)
auto cards_swapped = std::map<card, int>();
std::ranges::transform(hand, hand.begin(), [&cards_swapped](card const c) {
if (c.colour != colour::blue) {
return c;
}
note_swaps(cards_swapped, c);
return card{colour::green, c.value};
});
CHECK(std::ranges::none_of(hand, [](card const c) {
return c.colour == colour::blue;
}));
Capturing by reference
[&cards_swapped](card const c) {
// ...
}
Finishing off the example
{
REQUIRE(cards_swapped.contains(blue_number));
CHECK(cards_swapped.at(blue_number) == 4);
auto const green_number = card{colour::green, value::number};
CHECK(std::ranges::count(hand, green_number) == 4);
}
{
REQUIRE(cards_swapped.contains(blue_skip));
CHECK(cards_swapped.at(blue_skip) == 2);
auto const green_skip = card{colour::green, value::skip};
CHECK(std::ranges::count(hand, green_skip) == 2);
}
[](auto const& x, auto const& y) {
return x == y;
}
// #include <functional>
std::ranges::equal_to()
// #include <functional>
std::plus()
[](auto const& x, auto const& y) {
return x + y;
}
Constructing a vector of one type from a vector of another type
std::vector<double> standard_deviation_distribution();
static_cast<std::vector<int>>(standard_deviation_distribution());
Compile-time error: can't construct a vector<int>
from a vector<double>
Constructing a vector of one type from a vector of another type
std::vector<double> standard_deviation_distribution();
auto const intermediate = standard_deviation_distribution();
std::vector<int>(intermediate.begin(), intermediate.end());
Captures vs parameters
[blue_number](card const x) {
return x == blue_number;
}
auto const first_ten = std::vector<int>{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
};
auto const first_hundred = std::vector<int>{
0, 1, 2, 3, /* ... */, 99,
};
auto const first_thousand = std::vector<int>{
0, 1, 2, 3, /* ... */, 999,
};
Generating a sequence of integers on demand
// #include <numeric>
auto first_10k = std::vector<int>(10'000);
// populates vector with values [0, 10'000)
// all values exist at once
std::iota(first_10k.begin(), first_10k.end(), 0);
// #include <ranges>
auto first_10k = std::views::iota(0, 10'000);
// can be used like a vector
// only "current" int exists at any given time
// like `range(100)` in Python 3
All algorithms have an overload where they take a begin and end pair, but only the std::ranges
ones have range-based overloads too.
// #include <ranges>
namespace views = stdv;
auto is_blue = [](card const c) { return c.colour == colour::blue; };
auto all_blue = hand | stdv::filter(is_blue);
auto const expected = std::vector<card>{
blue_number,
blue_number,
blue_skip,
blue_number,
blue_number,
blue_skip,
};
CHECK(std::ranges::equal(expected, actual));
// #include <ranges>
auto const is_blue_card = [](card const c) { return c.colour == colour::blue; };
{
auto const result = std::ranges::find_if(hand, is_blue_card);
REQUIRE(result != hand.end());
CHECK(*result == blue_number);
}
Reversing
// #include <ranges>
namespace stdv = std::views;
auto const is_blue_card = [](card const c) { return c.colour == colour::blue; };
{
auto const result = std::ranges::find_if(hand, is_blue_card);
REQUIRE(result != hand.end());
CHECK(*result == blue_number);
}
{
auto back_to_front = hand | stdv::reverse;
auto const result = std::ranges::find_if(back_to_front, is_blue_card);
REQUIRE(result != back_to_front.rend())
CHECK(*result == blue_skip);
}
// #include <ranges>
namespace stdv = std::views;
auto const front3 = hand | stdv::take(3);
auto const expeceted std::vector<card>{
red_number,
blue_number,
green_draw_two,
};
CHECK(std::ranges::equal(front3, expected));
// #include <ranges>
namespace stdv = std::views;
auto const back6 = hand | stdv::drop(3);
auto const expected = std::vector<card>{
blue_number,
blue_skip,
yellow_draw_four,
blue_number,
blue_number,
blue_skip,
};
CHECK(std::ranges::equal(back6, expected));
Operation
Array-like
Node-based
Iterator
Writeable iterators
Iteration type
unsigned int
node*
unspecified
Write
v[i]
i->value
*i
++i
Successor
i = i->next
++i
Advance
j = i + n < v.size()
? i + n
: v.size();
std::ranges::next(i, s, n)
j = i->successor(n)
Comparison
i < v.size()
i != nullptr
i != s
// #include <algorithm>
void reset_scores(std::vector<int>& scores) {
std::ranges::fill(scores, 0);
}
// #include <algorithm>
auto chars_to_words(std::vector<char> const& from,
std::string& to) {
return std::ranges::copy(from, to.begin());
}
What happens when from.size() > to.size()
?
Copying values from one range to another, existing range
H
e
l
o
t
h
e
r
e
l
From:
To:
Insert iterators
// #include <iterator>
auto to = std::vector<char>();
REQUIRE(to.empty());
ranges::copy(from, std::back_inserter(to));
CHECK(to == expected);
Works on containers with a push_back member function like vector's
Insert iterators
auto to = std::vector<char>(5);
REQUIRE(from.size() > to.size());
REQUIRE(not to.empty());
to.assign(from.begin(), from.end());
CHECK(to == expected);
Insert iterators
Works on containers that have push_back
(e.g. std::vector
, std::string
)
Works on containers that have push_front
(e.g. std::deque
, std::list
)
Works on containers that have insert
(e.g. all the above, std::unordered_set
/map
)
auto some_numbers = std::vector<int>{0, 1, 2, 7, 8, 9};
auto const missing = std::vector<int>{3, 4, 5, 6};
auto non_uniform_gap = std::ranges::adjacent_find(some_numbers,
[](int const x, int const y) { return y - x != 1; });
some_numbers.insert(std::ranges::next(non_uniform_gap),
missing.begin(),
missing.end());
Could we have used iota?
auto some_numbers = std::vector<int>{0, 1, 2, 7, 8, 9};
auto const missing = stdv::iota(3, 7);
auto non_uniform_gap = std::ranges::adjacent_find(some_numbers,
[](int const x, int const y) { return y - x != 1; });
some_numbers.insert(std::ranges::next(non_uniform_gap),
missing.begin(),
missing.end());
This won't work because vector::insert
expects begin()
and end()
to return the same type, but missing
's begin()
and end()
return different types.
views::common gives us a "common range"
auto some_numbers = std::vector<int>{0, 1, 2, 7, 8, 9};
auto const missing = stdv::iota(3, 7)
| stdv::common;
auto non_uniform_gap = std::ranges::adjacent_find(some_numbers,
[](int const x, int const y) { return y - x != 1; });
some_numbers.insert(std::ranges::next(non_uniform_gap),
missing.begin(),
missing.end());
std::views::common
adapts the previous slide's missing into a type whose begin()
and end()
member functions return the same type!
If the interface is in namespace std::ranges
or std::views
, then you don't need to pipe your view to stdv::common
. Check the documentation if the interface is in namespace std
.
Mutable iterators are iterators with both a read operation and a write operation.
What's going on here?
auto from = std::vector<int>(10);
auto to = std::vector<int>(10);
// ...
// We would use ranges::copy IRL
for (auto i = from.begin(), j = to.begin();
i != from.end() and j != to.end(); ++i, ++j)
{
*i = *j;
}
i
is the read iterator, not the write one!
Iterator kinds
// mutable iterator (similar to `T&`)
std::vector<T>::iterator
// read-only iterator (similar to `T const&`)
std::vector<T>::const_iterator
Let T be the placeholder for any type.
Constant containers only have const_iterator
Getting a const_iterator
from a mutable vector
auto from = std::vector<int>(10);
auto to = std::vector<int>(10);
// ...
// We would use ranges::copy IRL
auto i = from.cbegin();
for (auto j = to.begin();
i != from.cend() and j != to.end(); ++i, ++j)
*i = *j; // compile-time error: can't write to a const_iterator
}
COMP6771 21T2 - 2.1 - C++ Standard Library
By cs6771
COMP6771 21T2 - 2.1 - C++ Standard Library
- 548