Standard algorithms

C++ · Practical · 15 min

Named algorithms are clearer, tested and often faster than raw loops, and every C++ reviewer expects you to know them.

The headers <algorithm> and <numeric> hold over a hundred generic algorithms that operate on iterator ranges, usually customised with a lambda. Knowing the common ones turns ten-line loops into one line that states its intent.

  • Searching: find, find_if, any_of/all_of/none_of, count_if, and on sorted ranges binary_search, lower_bound, equal_range.
  • Sorting: sort (not stable), stable_sort, partial_sort, nth_element (k-th element in O(n)), is_sorted.
  • Transforming: transform, copy_if, fill, iota, reverse, rotate, unique.
  • Reducing: accumulate and reduce (<numeric>), min_element, max_element, minmax_element.

Comparators must define a strict weak ordering: use <, never <=, or std::sort can misbehave and even crash.

Algorithms never change the size of a container, since they only see iterators. remove_if and unique move kept elements to the front and return the new logical end; you must then call erase to shrink the container (the erase-remove idiom). For the remove case, C++20's std::erase_if does both steps.

Watch accumulate's initial value: its type is the type of the accumulator, so accumulate(v.begin(), v.end(), 0) over doubles truncates every step to int. Pass 0.0.

Output algorithms like transform and copy write to an output iterator and assume there's room: size the destination first or use std::back_inserter(dest).

Since C++17, many algorithms accept an execution policy such as std::execution::par to run in parallel. The C++20 range versions in the Advanced lessons take whole containers instead of iterator pairs.

Example

#include <algorithm>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>

struct Order { std::string customer; double amount; };

int main() {
    std::vector<Order> orders{{"ann", 30.0}, {"bob", 120.5}, {"cy", 75.0}, {"ann", 9.5}};

    std::sort(orders.begin(), orders.end(),
              [](const Order& a, const Order& b) { return a.amount > b.amount; });
    std::cout << orders.front().customer << '\n';                     // bob

    double total = std::accumulate(orders.begin(), orders.end(), 0.0,  // 0.0, not 0!
        [](double sum, const Order& o) { return sum + o.amount; });
    bool any_big = std::any_of(orders.begin(), orders.end(),
                               [](const Order& o) { return o.amount > 100; });

    std::vector<std::string> names;
    std::transform(orders.begin(), orders.end(), std::back_inserter(names),
                   [](const Order& o) { return o.customer; });
    std::sort(names.begin(), names.end());
    names.erase(std::unique(names.begin(), names.end()), names.end());  // dedupe
    std::cout << total << ' ' << any_big << ' ' << names.size() << '\n'; // 235 1 3
}

Remember

  • Prefer a named algorithm to a raw loop: it states intent
  • remove_if/unique don't shrink the container — follow with erase (or use std::erase_if)
  • Comparators must be strict (<), and accumulate's initial value sets the result type

Watch out for

  • Passing 0 instead of 0.0 to std::accumulate over doubles, silently truncating the sum.

Check yourself

  1. v is {1, 2, 3, 2} and you call `std::remove(v.begin(), v.end(), 2);` without erase. What is v.size() afterwards?
  2. What's wrong with `std::sort(v.begin(), v.end(), [](int a, int b) { return a <= b; });`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: en.cppreference.com/w/cpp/algorithm