17.iterators Containers Alg
17.iterators Containers Alg
Programming
17. Containers, Iterators,
Ranges, and Algorithms
Federico Busato
2023-11-14
Table of Context
2 Sequence Containers
std::array
std::vector
std::deque
std::list
std::forward list
1/70
Table of Context
3 Associative Containers
std::set
std::map
std::multiset
4 Container Adaptors
std::stack, std::queue, std::priority queue
5 View
std::span
2/70
Table of Context
9 C++20 Ranges
Key Concepts
Range View
Range Adaptor
Range Factory
Range Algorithms
Range Actions
4/70
Containers and
Iterators
Containers and Iterators
Container
A container is a class, a data structure, or an abstract data type, whose instances
are collections of other objects
• Containers store objects following specific access rules
Iterator
An iterator is an object allowing to traverse a container
• Iterators are a generalization of pointers
• A pointer is the simplest iterator and it supports all its operations
• STL containers eliminate redundancy, and save time avoiding to write your own
code (productivity)
• STL containers are implemented correctly, and they do not need to spend time to
debug (reliability)
• STL containers are well-implemented and fast
• STL containers do not require external libraries
• STL containers share common interfaces, making it simple to utilize different
containers without looking up member function definitions
• STL containers are well-documented and easily understood by other developers,
improving the understandability and maintainability
• STL containers are thread safe. Sharing objects across threads preserve the
consistency of the container 6/70
Container Properties
C++ Standard Template Library (STL) Containers have the following properties:
• Default constructor
• Destructor
• Copy constructor and assignment (deep copy)
• Iterator methods begin() , end()
• Support std::swap
• Content-based and order equality ( ==, != )
• Lexicographic order comparison ( >, >=, <, <= )
• size() ∗ , empty() , and max size() methods
∗
except for std::forward list
7/70
Iterator Concept
• end() returns an iterator pointing to the end of the container (i.e. the element after the
last element)
Sequence containers are data structures storing objects of the same data type in a
linear mean manner
The STL Sequence Container types are:
• std::array provides a fixed-size contiguous array (on stack)
• std::vector provides a dynamic contiguous array ( constexpr in C++20)
• std::list provides a double-linked list
• std::deque provides a double-ended queue (implemented as array-of-array)
• std::forward list provides a single-linked list
While std::string is not included in most container lists, it actually meets the requirements
of a Sequence Container
9/70
embeddedartistry.com
std::array
10/70
std::vector
Other methods:
• resize() resizes the allocated elements of the container
• capacity() number of allocated elements
• reserve() resizes the allocated memory of the container (not size)
• shrink to fit() reallocate to remove unused capacity
11/70
• clear() removes all elements from the container (no reallocation)
std::deque
Other methods:
• resize() resizes the allocated elements of the container
• shrink to fit() reallocate to remove unused capacity
• clear() removes all elements from the container (no reallocation) 12/70
std::list
Other methods:
• resize() resizes the allocated elements of the container
• shrink to fit() reallocate to remove unused capacity
• clear() removes all elements from the container (no reallocation)
• remove() removes all elements satisfying specific criteria
• reverse() reverses the order of the elements
• unique() removes all consecutive duplicate elements
13/70
• sort() sorts the container elements
std::forward list
Other methods:
• resize() resizes the allocated elements of the container
• shrink to fit() reallocate to remove unused capacity
• clear() removes all elements from the container (no reallocation)
• remove() removes all elements satisfying specific criteria
• reverse() reverses the order of the elements
• unique() removes all consecutive duplicate elements
• sort() sorts the container elements 14/70
Supported Operations and Complexity
nt nt k
CONTAINERS h fro fro hb
ac bac
k
ert
(it )
se (it
)
pus pop pus pop ins era
std::array
std::vector O (1)∗ O (1)∗ O (n) O (n)
std::list O (1) O (1) O (1) O (1) O (1) O (1)
std::deque O (1)∗ O (1) O (1) O (1) O (1)∗ /O (n)† O (1)
std::forward list O (1) O (1) O (1) O (1)
∗
Amortized time †
Worst case (middle insertion) 15/70
std::array example
# include <array> // <--
# include <iostream> // std::array supports initialization
int main() { // only throw initialization list
std::array<int, 3> arr1 = { 5, 2, 3 };
std::array<int, 4> arr2 = { 1, 2 }; // [3]: 0, [4]: 0
// std::array<int, 3> arr3 = { 1, 2, 3, 4 }; // compiler error
std::array<int, 3> arr4(arr1); // copy constructor
std::array<int, 3> arr5 = arr1; // assign operator
arr5.fill(3); // equal to { 3, 3, 3 }
std::sort(arr1.begin(), arr1.end()); // arr1: 2, 3, 5
std::cout << (arr1 > arr2); // true
vec5.fill(3); // equal to { 3, 3, 3 }
std::cout << sizeof(vec1); // 24
std::cout << vec1.size(); // 3
for (const auto& it : vec1)
std::cout << it << ", "; // 2, 3, 5
int main() {
std::list<int> list1 { 2, 3, 2 };
std::list<std::string> list2 = { "abc", "efg" };
std::list<int> list3(2); // [0, 0]
std::list<int> list4{2}; // [2]
std::list<int> list5(2, -1); // [-1, -1]
list5.fill(3); // [3, 3]
list1.push_back(5); // [2, 3, 2, 5]
list1.merge(arr5); // [2, 3, 2, 5, 3, 3]
list1.remove(2); // [3, 5, 3, 3]
list1.unique(); // [3, 5, 3]
list1.sort(); // [3, 3, 5]
list1.reverse(); // [5, 3, 3]
}
18/70
std::deque example
int main() {
std::deque<int> queue1 { 2, 3, 2 };
std::deque<std::string> queue2 = { "abc", "efg" };
std::deque<int> queue3(2); // [0, 0]
std::deque<int> queue4{2}; // [2]
std::deque<int> queue5(2, -1); // [-1, -1]
queue5.fill(3); // [3, 3]
queue1.push_front(5); // [5, 2, 3, 2]
queue1[0]; // retuns 5
}
19/70
std::forward list example
int main() {
std::forward_list<int> flist1 { 2, 3, 2 };
std::forward_list<std::string> flist2 = { "abc", "efg" };
std::forward_list<int> flist3(2); // [0, 0]
std::forward_list<int> flist4{2}; // [2]
std::forward_list<int> flist5(2, -1); // [-1, -1]
flist5.fill(4); // [4, 4]
flist1.push_front(5); // [5, 2, 3, 2]
flist1.insert_after(flist1.begin(), 0); // [5, 0, 2, 3, 2]
flist1.erase_after(flist1.begin(), 0); // [5, 2, 3, 2]
flist1.remove(2); // [3, 5, 3, 3]
flist1.unique(); // [3, 5, 3]
flist1.sort(); // [3, 3, 5]
flist1.reverse(); // [5, 3, 3]
flist1.merge(flist5); // [5, 3, 3, 4, 4] 20/70
}
Associative
Containers
Overview
Sorted associative containers are typically implemented using red-black trees, while
unordered associative containers (C++11) are implemented using hash tables
Red-Black Tree
Hash Table
22/70
Supported Operations and Complexity
d
un
r bo nd
CONTAINERS t we ou
r e nt lo er b
se as u nd
in er co fi p
up
• count() returns the number of elements with key equal to a specified argument
• find() returns the element with key equal to a specified argument
• lower bound() returns an iterator pointing to the first element that is not less than key
• upper bound() returns an iterator pointing to the first element that is greater than key
23/70
Other Methods
Ordered/Unordered containers:
• equal range() returns a range containing all elements with the given key
• operator[]/at() returns a reference to the element having the specified key in the
container. A new element is generated in the set unless the key is found
Unordered containers:
24/70
std::set example
# include <set> // <--
# include <iostream>
int main() {
std::set<int> set1 { 5, 2, 3, 2, 7 };
std::set<int> set2 = { 2, 3, 2 };
std::set<std::string> set3 = { "abc", "efg" };
std::set<int> set4; // empty set
set2.erase(2); // [ 3 ]
set3.insert("hij"); // [ "abc", "efg", "hij" ]
for (const auto& it : set1)
std::cout << it << " "; // 2, 3, 5, 7 (sorted)
int main() {
std::map<std::string, int> map1 { {"bb", 5}, {"aa", 3} };
std::map<double, int> map2; // empty map
int main() {
std::multiset<int> mset1 {1, 2, 5, 2, 2};
std::multiset<double> mset2; // empty map
mset1.insert(5);
for (const auto& it : mset1)
std::cout << it << " "; // 1, 2, 2, 2, 5, 5
std::cout << mset1.count(2); // prints 3
it = mset1.lower_bound(4);
std::cout << *it; // 5
}
27/70
Container Adaptors
Overview
Container adaptors are interfaces for reducing the number of functionalities normally
available in a container
The underlying container of a container adaptors can be optionally specified in the
declaration
The STL Container Adaptors are:
• std::stack LIFO data structure
default underlying container: std::deque
• std::queue FIFO data structure
default underlying container: std::deque
• std::priority queue (max) priority queue
default underlying container: std::vector
28/70
Container Adaptors Methods
std::priority queue interface for a priority queue data structure (lookup to largest
element by default)
• top() accesses the top element
• push() inserts element at the end
• pop() removes the first element 29/70
Container Adaptor Examples
std::queue<int> queue1;
queue1.push(1); queue1.push(4); // [1, 4]
queue1.front(); // 1
queue1.pop(); // [4]
std::priority_queue<int> pqueue1;
pqueue1.push(1); queue1.push(5); queue1.push(4); // [5, 4, 1]
pqueue1.top(); // 5
pqueue1.pop(); // [4, 1] 30/70
}
View
std::span 1/3
template<
class T,
std::size_t Extent = std::dynamic_extent
> class span;
31/70
std::span 2/3
# include <span>
32/70
std::span 3/3
33/70
Implement a Custom
Iterator
Iterator Categories/Tags
34/70
Iterator Semantic 1/2
Iterator
• Copy Constructible It(const It&)
• Copy Assignable It operator=(const It&)
• Destructible ∼X()
• Dereferenceable It value& operator*()
• Pre-incrementable It& operator++()
Input/Output Iterator
• Satisfy Iterator
• Equality bool operator==(const It&)
• Inequality bool operator!=(const It&)
• Post-incrementable It operator++(int)
Forward Iterator
• Satisfy Input/Output Iterator
• Default constructible It() 35/70
Iterator Semantics 2/2
Bidirectional Iterator
• Satisfy Forward Iterator
• Pre/post-decrementable It& operator--(), It operator--(int)
int main() {
List list;
list.push_back(2);
list.push_back(4);
list.push_back(7);
std::cout << *std::find(list.begin(), list.end(), 4); // print 4
Range-based loops require: begin() , end() , pre-increment ++it , not equal comparison
it != end() , deferencing *it 37/70
Implement a Simple Iterator (List declaration) 2/6
39/70
Implement a Simple Iterator (Iterator declaration) 4/6
friend bool operator!=(const It& itA, const It& itA); // Not equal, needed to
// stop the traversing
It& operator++(); // Pre-increment
It operator++(int); // Post-increment
};
40/70
Implement a Simple Iterator (Iterator definition) 5/6
It& It::operator++() {
_ptr = _ptr->_next;
return *this;
}
It It::operator++(int) {
auto tmp = *this;
++(*this);
return tmp;
41/70
}
Implement a Simple Iterator (properties) 6/6
struct It {
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = value_t;
using pointer = value_t*;
using reference = value_t&;
...
};
42/70
internalpointers.com/post/writing-custom-iterators-modern-cpp
Iterator Utility
Methods
Iterator Operations 1/2
# include <iterator>
# include <iostream>
# include <vector>
# include <forward_list>
int main() {
std::vector<int> vector { 1, 2, 3 }; // random access iterator
std::advance(it2, 1);
std::cout << *it2; // 3
//--------------------------------------
std::forward_list<int> list { 1, 2, 3 }; // forward iterator
// std::prev(list.end(), 1); // compile error 45/70
}
Container Access Methods
C++11 provides a generic interface for containers, plain arrays, and std::initializer list
to access to the corresponding iterator.
Standard method .begin() , .end() etc., are not supported by plain array and initializer list
int main() {
int array[] = { 1, 2, 3 };
• difference type a type that can be used to identify distance between iterators
• value type the type of the values that can be obtained by dereferencing the
iterator. This type is void for output iterators
47/70
Iterator Traits 2/2
# include <iterator>
template<typename T>
void f(const T& list) {
using D = std::iterator_traits<T>::difference_type; // D is std::ptrdiff_t
// (pointer difference)
// (signed size_t)
using V = std::iterator_traits<T>::value_type; // V is double
using P = std::iterator_traits<T>::pointer; // P is double*
using R = std::iterator_traits<T>::reference; // R is double&
// C is BidirectionalIterator
using C = std::iterator_traits<T>::iterator_category;
}
int main() {
std::list<double> list;
f(list);
} 48/70
Algorithms Library
STL Algorithms Library
• STL Algorithm library allow great flexibility which makes included functions
suitable for solving real-world problem
• The user can adapt and customize the STL through the use of function objects
49/70
Examples 1/2
# include <algorithm>
# include <vector>
struct Unary {
bool operator()(int value) {
return value <= 6 && value >= 3;
}
};
struct Descending {
bool operator()(int a, int b) {
return a > b;
}
};
int main() {
std::vector<int> vector { 7, 2, 9, 4 };
// returns an iterator pointing to the first element in the range[3, 6]
std::find_if(vector.begin(), vector.end(), Unary());
// sort in descending order : { 9, 7, 4, 2 };
std::sort(vector.begin(), vector.end(), Descending());
} 50/70
Examples 2/2
int main() {
std::vector<int> vector { 7, 2, 9, 4 };
std::find
template<class InputIt, class T>
InputIt find(InputIt first, InputIt last, const T& value) {
for (; first != last; ++first) {
if (*first == value)
return first;
}
return last;
}
std::generate
template<class ForwardIt, class Generator>
void generate(ForwardIt first, ForwardIt last, Generator g) {
while (first != last)
*first++ = g();
} 52/70
Algorithm Library 1/5
• make heap(begin, end) Creates a max heap out of the range of elements
#include <algorithm>
A range is an object that provides the begin() and end() methods (an iterator +
a sentinel)
begin() returns an iterator, which can be incremented until it reaches end()
template<typename T>
concept range = requires(T& t) {
ranges::begin(t);
ranges::end(t);
};
60/70
Range View 1/2
A range view is a range defined on top of another range that transforms the
underlying way to access internal data
• Views do not own any data
Syntax:
range/view | view
61/70
Range View 2/2
#include <iostream>
#include <ranges>
#include <vector>
62/70
Range Adaptor 1/2
Range Adaptors are utilities to transform a range into a view with custom behaviors
Syntax:
adaptor(range/view, args...)
adaptor(args...)(range/view)
range/view | adaptor(args...) // preferred syntax
63/70
Range Adaptor 2/2
#include <iostream>
#include <ranges>
#include <vector>
64/70
Range Factory
65/70
Range Algorithms
66/70
Std Library - Range Algorithms
Algorithm Operators and Projections
# include <algorithm>
# include <vector>
struct Data {
char value1;
int value2;
};
67/70
Algorithms and Views
68/70
Range Actions 1/2
The range actions mimic std algorithms and range algorithms adding the
composability property
69/70
Range Actions 2/2
# include <algorithm>
# include <vector>
std::vector<int> vec{3, 5, 6, 3, 5}
// in-place
vec = vec | actions::sort // 3, 3, 5, 5, 6
| actions::unique; // 3, 5, 6
vec |= actions::sort // 3, 3, 5, 5, 6
| actions::unique; // 3, 5, 6
// out-of-place
auto vec2 = std::move(vec) | actions::sort // 3, 3, 5, 5, 6
| actions::unique; // 3, 5, 6
70/70