Skip to content

C++ Standard Template Library (STL)

Chapter 21

Introduction

Introduction to the STL

  • The standard C++ is equipped with a Standard Template Library
    (STL)
    .

  • The STL includes class templates to process lists, stacks, and queues.

  • In this lecture, you will:

    • Be introduced the Standard Template Library (STL)

    • Become familiar with the three basic components of the STL: containers, iterators, and algorithms.

    • Become familiar with basic operations on vector objects

Components of the STL

The STL consists of three categories of components:

  1. Containers are class templates to manage objects of a given type.
  2. Iterators are used to step through the elements of a container.
  3. Algorithms are used to manipulate data.
STL Components
The STL consists of three categories of components
STL Components
The STL consists of three categories of components

Available Containers Types

  • Sequence Containers implement data structures that can be accessed sequentially.

  • Ordered Associative Containers implement sorted data structures that can be quickly searched ( complexity).

  • Unordered Associative Containers implement unsorted (hashed) data structures that can be quickly searched ( on average, worst-case complexity).

  • Adapters provide a different interface for sequential containers.

  • Views provide flexible lightweight non-owning access to contiguous contains.

Available Sequence Containers Types in C++20

Sequence ContainersHeader FileIterator Support
array<array>Random access
vector<vector>Random access
deque<deque>Random access
list<list>Bidirectional
forward_list<forward_list>Forward

Available Associative Containers Types in C++20

Associative ContainersHeader FileIterator Support
map<map>Bidirectional
multimap<map>Bidirectional
set<set>Bidirectional
multiset<set>Bidirectional
unordered_map<unordered_map>Bidirectional
unordered_multimap<unordered_map>Bidirectional
unordered_set<unordered_set>Bidirectional
unordered_multiset<unordered_set>Bidirectional

Available Adpaters and Views Types in C++20

AdaptersHeader FileIterator Support
stack<stack>No iterator support
queue<queue>No iterator support
priority queue<queue>No iterator support
ViewsHeader FileIterator Support
span<span>Bidirectional

Sequence Containers in the STL

Sequence container: every object in the container has a specific position.

Three predefined sequence containers:

  1. array – encapsulates a fixed size array

  2. vector – a list with elements stored contiguously

  3. deque – a double-ended queue

  4. list – usually a doubly-linked list

  5. forward_list – usually a singly-linked list

vector

Declaring a vector

  • Stores and manages its objects in a dynamic array

  • Must use: #include <vector>

  • To define an object of type vector, specify the type of the object

  • Examples:

    cpp
    std::vector<int> intList;
    std::vector<std::string> stringList;
  • vector contains several constructors.

Constructors for a vector

There are 10 different constructors for vector; Here are some highlights.

  • vector();
    Default constructor. Constructs an empty container.

  • vector(size_type count, const T& value = T());
    Constructs the container with count copies of elements with value value.

  • template<typename InputIt>
    vector(InputIt first, InputIt last);
    Constructs the container with the contents of the range [first, last).

  • vector(const vector& other);
    Copy constructor.

Operations on a vector

  • Basic vector operations

    • Item insertion
      insert(iterator pos, T& value); push_back(const T& value); etc.

    • Item deletion
      erase(iterator pos); pop_back(); etc.

    • Stepping through the elements

      cpp
      at(int index) // Random access value.
      operator[](int index); // Random access reference
      front(); // the first element
      back();  // the last element
  • Vector elements can be processed just as they can in an array.

Example Operations on a vector

Vector Initialization
std::vector<int> vec{12, 7, 9, 21, 13};
Vector Initialization
std::vector<int> vec{12, 7, 9, 21, 13};
Pop the Back of a Vector
vec.pop_back();
Pop the Back of a Vector
vec.pop_back();
Push the Back of a Vector
vec.push_back(15);
Push the Back of a Vector
vec.push_back(15);
Access the first and forth element of a vector
Access the first and forth element of a vector.
Access the first and forth element of a vector
Access the first and forth element of a vector.`

Capacity/Size of a vector

MethodDescription
empty()returns true if the container is empty and false otherwise
size()returns the number of elements
max_size()returns the maximum possible number of elements
reserve()reserves storage
capacity()returns the number of elements that can be held in currently allocated storage
shrink_to_fit()reduces memory usage by freeing unused memory

Example use of the vector Container

cpp
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v(3);    // a vector of size 3
    v[0] = 23;
    v[1] = 12;
    v[2] = 9;                 // vector full
    v.push_back(17);          // append a new value
    for (auto el : v)         // range-based for loop.
    std::cout << el << ' '; // access to i-th el
    std::cout << std::endl;
    v[2] = v[3];              // random access
}

Example use of the vector Container

cpp
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v{ 12, 3, 17, 8 }; // initialize
    while (!v.empty())                  // until empty
    {
        std::cout << v.back() << ' '; // output last el
        v.pop_back();                 // delete last el
    }
    std::cout << std::endl;
}

Other Sequence Containers

The deque (Double-Ended Queue) Container

std::deque is another indexed sequence container, which is
accessed using #include <deque>

  • Allows fast insertion and deletion at both end ends. Slower insertion in the middle.

  • Insertion and deletion at either end of a deque never invalidates pointers or references to the rest of the elements.

  • The elements of a deque are not stored contiguously, but random access is .

The list Container

std::list is another indexed sequence container, which is accessed using
#include <list>

  • Allows constant-time insertion and deletion from anywhere in the container, which never invalidates pointers or references to the rest of the elements.

  • Fast random access is not supported.

  • Usually implemented as a doubly-linked list.

  • Less space efficient.

Iterators

Iterators for Container

  • Iterators are pointer-like entities that are used to access individual elements in a container.

  • Often they are used to move sequentially from element to element, a process called iterating through a container.

Iterators to a  Container
Iterators to a vector Container
Iterators to a  Container
Iterators to a vector Container
cpp
std::vector<int> vec { 17, 5, 23, 12, 9, 11, 13, 9 };

// Declares an iterator into a int vector container
std::vector<int>::iterator intVecIter = vec.begin();

// Advance the iterator to the second element.
++intVecIter;

// Access the second element and change its value.
*intVecIter = -3;

// Second iterator positioned on the last element.
std::vector<int>::iterator intVecIter2 = vec.end();
--intVecIter2;

Containers have begin and end Iterator Functions

  • begin(): returns an iterator to the beginning of the container.

  • end(): returns an iterator to just past the last value.

    begin and end iterators
    Begin and end iterators.
    begin and end iterators
    Begin and end iterators.
cpp
std::vector<int> vec { 17, 4, 23, 12, 9, 7, 11, 13 };

std::vector<int>::iterator intVecIter1 = vec.begin();
std::vector<int>::iterator intVecIter2 = vec.end();
--intVecIter2; // move to last element

Iterator Example

cpp
template <typename Type>
Type maxValue(const vector<Type> &vec)
{
  vector<int>::const_iterator iter = vec.cbegin();
  Type max = *iter;
  for (++iter; iter != vec.cend(); ++iter)
  {
    if (*iter > max)
      max = *iter;
  }
  return max;
}

std::vector<std::string> words { "The", "fun", "has", "just", "begun" };
cout << "max of words = " << maxValue(words);

Types of Iterators

Every algorithm requires an iterator with a certain level of capability. For example, to use the [] operator you need a random access iterator.

  1. Input iterators have read access; step forward element-by-element.

  2. Output iterators have write access; step forward element-by-element.

  3. Forward iterators have the functionality of input and most of the functionality of output iterators.

  4. Bidirectional iterators are forward iterators that can also go backward.

  5. Random access iterators are bidirectional iterators that can randomly process the elements of a container.

  6. As of C++20, there are additional iterators based on concepts, which differ from C++17.

https://en.cppreference.com/w/cpp/iterator

Iterator Operations

TypeAccessReadWriteIterate
Inputx = *i++
Outputx = *i*i = x++
Forwardx = *i*i = x++
Bidirectionalx = *i*i = x++, --
Random Access[ ]x = *i*i = x++, --

Helper Iterator Operators

The <iterator> library includes many overloaded operators and functions to use with iterators. Some include:

  • advance – advances an iterator by a given distance

  • next – increment an iterator

  • prev – decrement an iterator

Benefits of Iterators

  • Convenience in Programming:

    • Provide common usage for any container (the internal structure of a container does not matter).

    • Provide read and/or write access to each element’s values.

  • Reusable Code:
    Iterator algorithms are not dependent on the container type.

  • Dynamic Processing:
    Easily dynamically allocate as well as delete the memory.

Associative Containers

Ordered Associative Containers

Store elements in sorted order according to some ordering criteria
(often using a type of binary tree).

Associative ContainersHeader FileIterator Support
map<map>Bidirectional
multimap<map>Bidirectional
set<set>Bidirectional
multiset<set>Bidirectional
  • set stores a set of unique elements, while map stores key-value pairs with unique keys.

  • multiset and multimap allow duplicates.

Sets Example

cpp
#include <set>
int main()
{
  std::set<std::string, std::less<std::string> > nameSet{ "Ole",
       "Hedvig", "Juan", "Lars", "Guido", "Ann"};

  nameSet.insert("Patric"); // insert another name
  nameSet.erase("Juan"); // removes an element

  // Find the matching name in set.
  std::string searchName;
  std::cin >> searchName;
  std::set<std::string, std::less<string> >::iterator iter =
    nameSet.find(searchName);

  cout << searchName << " is";

  if (iter == nameSet.end()) // check if found
     std::cout << " NOT";

  std::cout << " in the set!\n";
}

Sets Example

cpp
#include <set>

int main()
{
  std::set<std::string, std::less<std::string> > nameSet {
    "Ole", "Hedvig", "Juan", "Lars", "Guido", "Patric",
    "Maria", "Ann" };

  // Set iterator to lower start value "K"
  std::set<std::string, std::less<std::string> >::iterator
    iter = nameSet.lower_bound("K");

  // Display: Lars, Maria, Ole, Patric
  while (iter != nameSet.upper_bound("Q"))
    cout << *iter++ << ", ";

  std::cout << std::endl;
}

Maps Example

cpp
#include <map>
#include <algorithm>
int main()
{
  std::map<std::string, int> contacts {
    {"Ole", 75643}, {"Hedvig", 83268}, {"Juan", 97353},
    {"Lars", 87353}, {"Guido", 19988}, {"Patric", 76455},
    {"Maria", 77443}, {"Ann", 12221}};

  std::cout << "Lars phone number is " << contacts["Lars"]
    << '\n';

  for(auto iter = contacts.begin(); iter != contacts.end(); iter++)
    std::cout << iter->first << " : " << iter->second << '\n';
}

Adapters

Container Adapters

Containers to accommodate special situations.

AdaptersHeader FileIterator Support
stack<stack>No iterator support
queue<queue>No iterator support
priority queue<queue>No iterator support
  • Do not support any type of iterator.

The std::span Container View

Two spans pointing to different ranges of elements in an array.
Two spans pointing to different ranges of elements in an array.
Two spans pointing to different ranges of elements in an array.
Two spans pointing to different ranges of elements in an array.

Algorithms in the STL

Many algorithms that work on any STL container are found in the <algorithm> library.

  • Copy
  • Partition
  • Sort
  • Search

These algorithms are provided generically to support many container types.

STL Algorithm Classifications

  • Non-modifying algorithms do not modify the elements of the container

  • Modifying algorithms modify the elements of the container

  • Numeric algorithms perform numeric calculations on elements of a container.

  • Heap algorithms are Heapsort algorithm that sorts contiguous data.

Non-Modifying Sequence Algorithms

MethodDescription
all_of, any_of, none_ofchecks if a predicate is true for all, any or none of the elements in a range
for_eachapplies a function to a range of elements
for_each_napplies a function object to the first n elements of a sequence
count, count_ifreturns the number of elements satisfying specific criteria
mismatchfinds the first position where two ranges differ
find, find_if, find_if_notfinds the first element satisfying specific criteria
find_endfinds the last sequence of elements in a certain range
find_first_ofsearches for any one of a set of elements
adjacent_findfinds the first two adjacent items that are equal (or satisfy a given predicate)
searchsearches for a range of elements
search_nsearches a range for a number of consecutive copies of an element

Example: The for_each() Algorithm

cpp
#include <vector>
#include <algorithm>
#include <iostream>
void show(int n) {
    std::cout << n << ", ";
}

int main() {
    // initialize vector with a list of values
    std::vector<int> v{ 12, 3, 17, 8 };

    // apply function show to each element of vector v
    std::for_each (v.begin(), v.end(), show);
}

Example: The find() Algorithm

cpp
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
  std::vector<int> v{ 12, 3, 17, 8, 34, 56, 9 };
  std::vector<int>::iterator iter;
  int key;

  std::cout << "Enter value: ";
  std::cin >> key;
  iter = std::find(v.begin(), v.end(), key);
  std::cout << "Element " << key << " is ";
  if (iter == v.end()) // value was NOT found
     std::cout << "not";
  std::cout << " in vector v.\n";
}

Example: The find_if() Algorithm

cpp
#include <vector>
#include <algorithm>
#include <iostream>
bool inRange(int n) { return (n > 21) && (n < 36); };

int main() {
  std::vector<int> v{ 12, 3, 17, 8, 34, 56, 9 };
  std::vector<int>::iterator iter;
  iter = std::find_if(v.begin(), v.end(), inRange);

  // finds an element in v for which inRange is true
  if (iter != v.end()) // found the element
    std::cout << "found " << *iter << '\n';
  else
    std::cout << "not found\n";
}

Example: The count_if() Algorithm

cpp
#include <vector>
#include <algorithm>
#include <iostream>
bool inRange(int n) { return (n > 14) && (n < 36); };

int main() {
  std::vector<int> v{ 12, 3, 17, 8, 34, 56, 9 };
  std::vector<int>::iterator iter;

  // counts element in v for which inRange is true
  const int N = std::count_if(v.begin(),v.end(), inRange);
  std::cout << "found " << N << " values between 14 and 36.\n";
}

Minimum/Maximum Algorithms

MethodDescription
maxreturns the greater of the given values
max_elementreturns the largest element in a range
minreturns the smaller of the given values
min_elementreturns the smallest element in a range
minmaxreturns the smaller and larger of two elements
minmax_elementreturns the smallest and the largest elements in a range
clampclamps a value between a pair of boundary values

Set Algorithms (on Sorted Ranges)

MethodDescription
includesreturns true if one sequence is a subsequence of another
set_differencecomputes the difference between two sets
set_intersectioncomputes the intersection of two sets
set_symmetric_differencecomputes the symmetric difference between two sets
set_unioncomputes the union of two sets

Modifying Sequence Algorithms

MethodDescription
copy, copy_ifcopies a range of elements to a new location
copy_ncopies a number of elements to a new location
copy_backwardcopies a range of elements in backward order
movemoves a range of elements to a new location
move_backwardmoves a range of elements to a new location in backward order
fillcopy-assigns the given value to every element in a range
fill_ncopy-assigns the given value to N elements in a range
transformapplies a function to a range of elements, storing results in a destination range
generateassigns the results of successive function calls to every element in a range
generate_nassigns the results of successive function calls to N elements in a range
remove, remove_ifremoves elements satisfying specific criteria

Example: The copy() Algorithm

The copy algorithm overwrites the existing contents when using regular iterators.

cpp
#include <list>
#include <algorithm>

int main() {
  std::list<int> odds{ 1, 3, 5, 7, 9 };
  std::list<int> result{ 2, 4, 6, 8, 10 };

  // copy contents of odds to result overwriting the elements in result
  std::copy(odds.begin(), odds.end(), result.begin());
  // result = { 1, 3, 5, 7, 9 }
}

Example: The copy() Algorithm

With insert operators, you can modify the behavior of the copy algorithm.

MethodDescription
back_inserterinserts new elements at the end
front_inserterinserts new elements at the beginning
inserterinserts new elements at a specified location
cpp
std::list<int> l1{ 1, 3, 5, 7, 9 }, l2{ 2, 4, 6, 8, 10 };
cpp
std::copy(l1.begin(), l1.end(), std::back_inserter(l2));
// l2 = { 2, 4, 6, 8, 10, 1, 3, 5, 7, 9  }
cpp
std::copy(l1.begin(), l1.end(), std::front_inserter(l2));
// l2 = { 9, 7, 5, 3, 1, 2, 4, 6, 8, 10 }
cpp
std::copy(l1.begin(), l1.end(), std::inserter(l2, l2.begin()));
// l2 = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 10 }

Example: The copy() Algorithm with Output Iterator

cpp
#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>

int main() {
  std::vector<int> vector {10, 20, 30, 40, 50, 60 70};
  std::ostream_iterator<int> outIter{std::cout, ", "};

  std::copy(vector.begin(), vector.end(), outIter);
}

Modifying Sequence Algorithms

MethodDescription
remove_copy, remove_copy_ifcopies a range of elements omitting those that satisfy specific criteria
replace, replace_ifreplaces all values satisfying specific criteria with another value
replace_copy, replace_copy_ifcopies a range, replacing elements satisfying specific criteria with another value
swapswaps the values of two objects
swap_rangesswaps two ranges of elements
iter_swapswaps the elements pointed to by two iterators
reversereverses the order of elements in a range
reverse_copycreates a copy of a range that is reversed
rotaterotates the order of elements in a range
rotate_copycopies and rotate a range of elements

Example: The sort() and merge() Algorithms

cpp
#include <list>
#include <algorithm>
int main() {
  std::list<int>  l1 { 6, 4, 9, 1, 7 }
  std::list<int>  l2 { 4, 2, 1, 3, 8 };
  l1.sort(); // l1 = {1, 4, 6, 7, 9}
  l2.sort(); // l2 = {1, 2, 3, 4, 8 }
  l1.merge(l2);  // merges l2 into l1
  // l1 = { 1, 1, 2, 3, 4, 4, 6, 7, 8, 9},  l2 = {}
}

Numeric Algorithms in <numeric>

MethodDescription
iotafills a range with successive increments of the starting value
accumulatesums up a range of elements
inner_productcomputes the inner product of two ranges of elements
adjacent_differencecomputes the differences between adjacent elements in a range
partial_sumcomputes the partial sum of a range of elements
reducesimilar to std::accumulate, except out of order
exclusive_scanexcludes the ith input element from the ith sum
inclusive_scanincludes the ith input element in the ith sum
transform_reduceapplies an invocable, then reduces out of order
transform_exclusive_scanapplies an invocable, then calculates exclusive scan
transform_inclusive_scanapplies an invocable, then calculates inclusive scan

Heap Algorithms

Built-in heapsort.
(You may not implement the Heap in your lab using these functions,
but you may use them for testing.)

MethodDescription
is_heapchecks if the given range is a max heap
is_heap_untilfinds the largest subrange that is a max heap
make_heapcreates a max heap out of a range of elements
push_heapadds an element to a max heap
pop_heapremoves the largest element from a max heap
sort_heapturns a max heap into a range of elements sorted in ascending order

Function Objects

Function Objects

  • Some algorithms like sort, merge, and accumulate can take a function object as an argument.

  • A function object is an object of a template class that has a single member function: the overloaded operator().

  • It is also possible to use user-written functions in place of predefined function objects.

cpp
#include <list>
#include <functional>
int main() {
  std::list<int> lst { 6, 4, 9, 1, 7 };
  lst.sort(std::greater<int>{}); // uses function object
  // for sorting in reverse order lst = { 9, 7, 6, 4, 1 }
}

In C++, Function Objects are often called functors, but that term has alternate means.

Function Objects

The accumulate algorithm accumulates values over a range (e.g., the sum).

cpp
#include <list>
#include <functional>
#include <numeric>
int main() {
  std::list<int> lst { 6, 4, 9, 1, 7 };
  int sum, fac;
  sum = std::accumulate(lst.begin(), lst.end(), 0,
    std::plus<int>{});
  sum = std::accumulate(lst.begin(), lst.end(), 0);
    // plus by default

  fac = std::accumulate(lst.begin(), lst.end(), 1,
    std::times<int>{});
}

User Defined Function Objects

cpp
template <typename Type>
struct SquaredSum  // user-defined function object
{
    Type operator()(Type n1, Type n2) { return n1 + n2 * n2; }
};
cpp
#include <complex>
#include <numeric>
#include <vector>
cpp
std::vector<std::complex<int>> vc{ {2, 3}, {1, 5}, {-2, 4} };

// computes the sum of squares of a vector of complex numbers
std::complex sum_vc = std::accumulate(vc.begin(), vc.end(),
          std::complex(0, 0), SquaredSum<std::complex<int>>{});

User Defined Function Objects

Unlike functions, function objects permit custom parameterization and statefullness.

cpp
std::vector<double> vals1 {2, 3, 1, 5, -2, 4};
std::vector<double> vals2 {9, 8, -6, -10, -1, 8, 6};

// Square each value in both vectors and calculate the total.
CalculateAverageOfPowers squared{2}; // Function Object
squared = std::for_each(vals1.begin(), vals1.end(), squared);
squared = std::for_each(vals2.begin(), vals2.end(), squared);

std::cout << ' ' << squared.getAverage() << '\n';

User Defined Function Objects

cpp
class CalculateAverageOfPowers
{
public:
  CalculateAverageOfPowers(double power) 
    : total{0}, count{0}, exponent{power} {}

  void operator() (double x) {
    total += pow(x, exponent);
    ++count;
  }

  auto getAverage() const { return total / count; }

private:
  double total;
  int count;
  double exponent;
};

Lab 17: The STL and Sorting

Let’s take a look at Lab 17.