C++

Chapter 9: Iterators and Algorithms — The Heart of the Standard Template Library (STL)

By Ali Naqi • November 17, 2025

Chapter 9: Iterators and Algorithms — The Heart of the Standard Template Library (STL)

Chapter 9: Iterators and Algorithms—The Heart of the Standard Template Library (STL)

You now know how to store data in dynamic, modern containers like std::vector and std::string. But the real power of the Standard Template Library (STL) comes from the second two pillars: **Iterators** and **Algorithms**.

The genius of the STL is that it completely decouples the structure of the data (the container) from the logic used to process the data (the algorithm). They are connected by **Iterators**, which act as a uniform interface. This means you can use the exact same std::sort function to sort a std::vector, a std::list (which we'll see later), or even an old-school C-style array. This uniformity is what makes modern C++ so incredibly productive.

Section 1: Iterators: The Generalized Pointer

An **iterator** is an object that behaves like a generalized pointer. It points to an element within a container, allowing you to access and traverse the elements sequentially.

begin() and end()

Every STL container provides two crucial methods that define the range for algorithms:

  1. **.begin()**: Returns an iterator pointing to the **first element** in the container.
  2. **.end()**: Returns an iterator pointing to the position **one past the last element**. This position is a conceptual placeholder and should never be dereferenced (read or written to). It acts as the stopping condition for traversal.

The fundamental operations you perform on an iterator are:

  • **Dereference (`*`):** Get the value of the element the iterator points to.
  • **Increment (`++`):** Move the iterator to the next element.

#include <vector>
#include <iostream>

void print_vector(const std::vector<int>& vec) {
    // Declares an iterator of the correct type
    std::vector<int>::const_iterator it; 
    
    // Loop from begin() up to (but not including) end()
    for (it = vec.begin(); it != vec.end(); ++it) {
        // Dereference the iterator to access the element value
        std::cout << *it << " ";
    }
    std::cout << std::endl;
}

// In main or another function:
// std::vector<int> data = {5, 1, 4, 2, 8};
// print_vector(data); // Output: 5 1 4 2 8 

While the traditional iterator loop is essential for understanding the concept, remember the modern C++ approach (range-based for loop) from the last chapter often makes code cleaner for simple traversals!

Section 2: The Power of STL Algorithms

Algorithms are functions defined in the <algorithm> header. They operate on ranges defined by a pair of iterators (e.g., `begin()` and `end()`). This means the algorithm doesn't care if the data is a vector, a list, or an array—it only needs to know where to start and where to stop.

Basic Algorithm Examples


#include <algorithm>
#include <vector>
#include <iostream>

void demonstrate_algorithms() {
    std::vector<int> numbers = {5, 1, 9, 3, 7};
    
    // --- 1. Sorting ---
    // Sorts the entire range from numbers.begin() up to numbers.end()
    std::sort(numbers.begin(), numbers.end()); 
    // numbers is now: {1, 3, 5, 7, 9}
    
    // --- 2. Searching ---
    // Search the range for the value 9
    auto it_found = std::find(numbers.begin(), numbers.end(), 9);
    
    if (it_found != numbers.end()) {
        std::cout << "Value 9 found!" << std::endl;
    }
    
    // --- 3. Counting ---
    // Count how many times the value 5 appears
    int count_fives = std::count(numbers.begin(), numbers.end(), 5);
    std::cout << "The count of 5s is: " << count_fives << std::endl;
}

Notice how clean and expressive the code becomes. You don't write the loop logic or the sorting routine; you simply call the named function that expresses your intent.

Section 3: Customizing Algorithms with Lambda Expressions

Many powerful algorithms, such as std::sort and std::find_if, can be customized by providing them with a third argument: a **callable object** that defines custom logic. In modern C++, the easiest way to provide this logic is with a **lambda expression**.

A lambda expression is a compact way to define an anonymous function object right where you need it. The general syntax is:

[capture list] (parameters) -> return_type { function body }

Example: Custom Sorting

Let's say we want to sort a vector of integers in **descending** order. The default `std::sort` does ascending order, so we need a custom comparison function.


#include <algorithm>
#include <vector>
#include <iostream>

void demonstrate_lambdas() {
    std::vector<int> numbers = {5, 1, 9, 3, 7};
    
    // Sort in descending order using a lambda
    std::sort(numbers.begin(), numbers.end(), 
              // Lambda starts here:
              [](int a, int b) { 
                  return a > b; // Custom logic: a is "less than" b if a > b
              } // Lambda ends here
    );
    
    // numbers is now: {9, 7, 5, 3, 1}
    for (int n : numbers) {
        std::cout << n << " ";
    }
    std::cout << std::endl;
}

The Capture List (`[]`)

The **capture list** (`[]`) is used to bring variables from the surrounding scope into the body of the lambda function.

  • **`[]`**: Captures nothing.
  • **`[x]`**: Captures variable `x` by value (makes a copy).
  • **`[&x]`**: Captures variable `x` by reference (allows modification).
  • **`[=]`**: Captures all surrounding variables by value.
  • **`[&]`**: Captures all surrounding variables by reference.

int threshold = 5;

// Finds the first element greater than the threshold (5)
auto it_gt_5 = std::find_if(numbers.begin(), numbers.end(), 
    // Capture 'threshold' by value (copy)
    [threshold](int n) { 
        return n > threshold; 
    } 
);

if (it_gt_5 != numbers.end()) {
    std::cout << "First element > 5 is: " << *it_gt_5 << std::endl;
}

Lambda expressions are a cornerstone of modern C++ programming. They make it easy to write highly expressive, localized, and context-aware logic directly alongside the STL algorithms that need it.

Section 4: Iterator Categories and Performance

Not all iterators are created equal. They are grouped into categories based on the operations they support, which determines what algorithms can be used on a container.

  • **Random Access Iterator (The Best):** Supports fast access to any element (`it + N`), comparison (`it < end()`), and the full range of arithmetic.
    • **Used by:** std::vector, std::string, C-style arrays.
    • **Algorithms that require it:** std::sort, std::shuffle, binary search functions.
  • **Bidirectional Iterator:** Supports moving forward (`++it`) and backward (`--it`).
    • **Used by:** std::list, std::set, std::map.
  • **Forward, Input, Output Iterators:** Support only forward movement or read/write access.
    • **Used by:** Containers like `std::forward_list` and stream operations.

The key takeaway is that because **std::vector** provides the superior **Random Access Iterator**, it can use the full spectrum of high-performance STL algorithms, making it the most versatile and generally preferred container.

Final Thoughts on Iterators and Algorithms

By combining containers, iterators, and algorithms, you are now writing efficient, modern C++ code. The focus shifts from managing memory and writing loop boilerplate to expressing *what* you want to accomplish using standard, optimized library functions.

The next step is to explore how to store and manage more complex, non-sequential data, which is where the other great STL containers come in.

In **Chapter 10**, we will move beyond vectors to discuss **Associative Containers**—specifically **std::map** and **std::set**—which use keys to organize data efficiently.