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();
}
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();
}
"Every line of code you don't write is bug-free!"
Code in a popular library is often:
We use the following extremely popular libraries
You are not expected to learn everything in these libraries.
We will instead cherry-pick certain useful components from each.
#include <catch2/catch.hpp>
TEST_CASE("empty vectors") { // Opens up a context for testing
}
#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.
}
#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.
}
}
#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]);
}
}
#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") {
}
}
}
#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") {
}
}
}
#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
}
}
}
// #include <iostream>
// Character output
std::cout << "The meaning of life is " << 42 << '\n'
<< "pi = " << 3.14159265 << '\n';
// #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.
// #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();
// #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";
}
// #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"
}
// #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?";
}
Containers
Algorithms
Ranges
Iterators
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).
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.
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.
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.
// #include <string>
auto const greeting = std::string("hello, world!");
// #include <string>
using namespace std::string_literals;
auto const greeting = "hello, world"s;
// #include <string>
int main() {
using namespace std::string_literals;
auto const greeting = "hello, world"s;
}
// #include <string>
auto const greeting = "hello" + "world" + '!';
std::vector
revisitedauto 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);
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);
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);
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);
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);
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);
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());
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;
};
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};
// #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);
// #include <stack>
CHECK(deck.top() == blue_number);
deck.pop();
CHECK(deck.top() == yellow_draw_four);
deck.pop();
// ...
auto const more_cards = deck;
REQUIRE(more_cards == deck);
deck.pop();
CHECK(more_cards != deck);
// #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);
// #include <stack>
CHECK(deck.front() == red_number);
deck.pop();
CHECK(deck.front() == green_draw_two);
deck.pop();
// ...
auto const more_cards = deck;
REQUIRE(more_cards == deck);
deck.pop();
CHECK(more_cards != deck);
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;) { ... }
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;
}
// 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;
}
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;
}
}
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;
}
...a doubly-linked list?
...a static vector?
...a double-ended queue?
...std::string?
...a skip list?
...a cord?
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.
Operation
Array-like
Node-based
Iterator
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)
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();
}
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);
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
// #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());
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);
Red number
Green draw 2
Blue number
Blue skip
Yellow draw 4
Blue number
Blue number
Blue skip
Position: 1
Value: green_draw_two
// #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);
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
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::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);
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
// #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
[](card const c) {
return c.colour == colour::blue;
}
[](card const c) -> bool {
return c.colour == colour::blue;
}
#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;
}));
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
[blue_number](card const x) {
return x == blue_number;
}
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);
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;
}
// #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;
}));
[&cards_swapped](card const c) {
// ...
}
{
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;
}
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>
std::vector<double> standard_deviation_distribution();
auto const intermediate = standard_deviation_distribution();
std::vector<int>(intermediate.begin(), intermediate.end());
[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,
};
// #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);
}
// #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
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()
?
H
e
l
o
t
h
e
r
e
l
From:
To:
// #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
auto to = std::vector<char>(5);
REQUIRE(from.size() > to.size());
REQUIRE(not to.empty());
to.assign(from.begin(), from.end());
CHECK(to == expected);
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());
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.
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
.
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!
// 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
const_iterator
from a mutable vectorauto 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
}