The <algorithm> header (plus <numeric>, <ranges>, <execution>) covers the
generic operations the STL provides over iterator ranges. This page is a quick
reference grouped by purpose, with robotics-flavored examples and a few sharp
edges to watch out for.
Conventions used below:
nis the size of the input range.- "Sorted-range" algorithms assume the input is already sorted by the same comparator you pass in — getting that wrong is a common bug.
- Code shown is C++20 unless a
// C++23marker is on the line.
| Function | Notes |
|---|---|
for_each, for_each_n |
Side-effect loop. Result is not propagated unless your callable captures or takes by reference. |
count, count_if |
Returns iterator_traits::difference_type (signed). |
find, find_if, find_if_not |
Linear scan; use lower_bound on sorted data. |
find_first_of |
Finds the first element from set B inside A. |
adjacent_find |
First position i where pred(a[i], a[i+1]) holds. |
search, search_n |
Subsequence search; pass a std::boyer_moore_searcher for fast substring scans. |
mismatch |
First index where two ranges differ — pair of iterators. |
equal, is_permutation |
Whole-range equality / unordered equality. |
all_of, any_of, none_of |
Predicate quantifiers. |
contains, contains_subrange |
C++23. Returns bool — clearer than find != end(). |
starts_with, ends_with |
C++23, in std::ranges::. Works on any range, not just string. |
// Count joints whose tracking error exceeds 1°.
std::vector<double> error_deg = read_joint_errors();
auto bad = std::count_if(error_deg.begin(), error_deg.end(),
[](double e){ return std::abs(e) > 1.0; });std::for_each returns nothing useful unless the lambda captures by reference —
a returned value from the lambda is silently discarded:
std::vector<int> v = {0,1,2,3,4,5};
std::for_each(v.begin(), v.end(), [](int x){ return x * 3; }); // no-op
std::for_each(v.begin(), v.end(), [](int& x){ x++; }); // mutates v| Function | Notes |
|---|---|
copy, copy_if, copy_n, copy_backward |
Pre-size the destination, or pass std::back_inserter. |
move, move_backward |
Same as copy* but invokes move ctor/assign. Source range left in a valid but unspecified state. |
fill, fill_n, generate, generate_n |
Bulk assignment / generator-based fill. |
transform |
Output = f(input) or Output = f(input1, input2). |
replace, replace_if, replace_copy* |
Substitute matching values. |
remove, remove_if, unique |
Do not shrink the container — pair with erase (or use std::erase/std::erase_if, C++20). |
reverse, rotate, shuffle |
In-place rearrangements. |
shift_left, shift_right |
C++20. Discards values that fall off the edge — handy for fixed-size sample windows. |
sample |
C++17. Random sample of size k without replacement. |
swap, iter_swap, swap_ranges |
Element-wise swap. |
exchange |
Returns the old value while assigning the new one — useful inside move constructors. |
std::remove does not actually remove anything; it overwrites the kept
elements at the front and returns the new logical end. You almost always
follow it with erase, or just use std::erase_if (C++20):
std::vector<int> v = {0,1,2,3,4,5,6,7,8,9};
// classic erase-remove
v.erase(std::remove_if(v.begin(), v.end(),
[](int x){ return x % 2 == 0; }),
v.end());
// C++20 one-liner — same effect
std::erase_if(v, [](int x){ return x % 2 == 0; });std::unique only collapses consecutive duplicates — sort first if you want
true deduplication.
std::array<double, 8> imu_window{};
// ... new sample arrives ...
std::shift_left(imu_window.begin(), imu_window.end(), 1);
imu_window.back() = latest_sample;struct Handle {
int fd;
Handle(Handle&& other) noexcept : fd(std::exchange(other.fd, -1)) {}
};| Function | Notes |
|---|---|
partition |
Rearranges so all pred==true come first. Not stable. |
stable_partition |
Stable but allocates. |
partition_copy |
Splits into two output ranges. |
partition_point |
Binary search for the boundary on an already-partitioned range. |
is_partitioned |
Predicate check. |
// Separate obstacle points (close range) from background — order inside each
// group does not matter, so use the non-stable form.
auto bound = std::partition(points.begin(), points.end(),
[](const auto& p){ return p.range < 5.0; });
// [begin, bound) are close-range, [bound, end) are far.| Function | Notes |
|---|---|
sort |
O(n log n) introsort. Unstable. Requires random-access iterators. |
stable_sort |
Stable, may allocate. |
partial_sort |
First k elements sorted; rest is unspecified. O(n log k). |
partial_sort_copy |
Same but writes to a separate output range. |
nth_element |
Places one element in its sorted position; partitions around it. O(n) average. |
is_sorted, is_sorted_until |
Predicate checks. |
Comparator must be a strict weak ordering. That means
cmp(a, a)is alwaysfalse. Passingstd::greater_equal<>{}orstd::less_equal<>{}tosort,nth_element,make_heap, etc. is undefined behavior — even if it looks like it works. Usestd::less<>{}orstd::greater<>{}.
// Find the 4 closest obstacles, sorted by range.
constexpr int K = 4;
std::vector<Obstacle> obs = scan();
std::partial_sort(obs.begin(), obs.begin() + K, obs.end(),
[](const auto& a, const auto& b){ return a.range < b.range; });
// obs[0..K) holds the K closest, in order.std::vector<int> v = {3,7,4,9,2,5,8,1,6};
auto mid = v.begin() + v.size()/2;
std::nth_element(v.begin(), mid, v.end());
// *mid is the median; elements left/right are < / > but unsorted.nth_element is the right tool for median filtering of sensor data — it's
O(n) average versus O(n log n) for a full sort.
| Function | Returns |
|---|---|
lower_bound |
First position where value could be inserted (>= value). |
upper_bound |
First position strictly greater than value. |
equal_range |
Pair {lower_bound, upper_bound}. |
binary_search |
bool only — usually prefer lower_bound and compare. |
std::vector<double> ts = sorted_timestamps();
// Index of the first sample at or after t_query.
auto it = std::lower_bound(ts.begin(), ts.end(), t_query);
auto idx = std::distance(ts.begin(), it);Pitfall: if the range is unsorted, results are undefined. These do log(n)
comparisons but O(n) iterator steps on non-random-access iterators — so on
std::list they degrade to linear and you should use the container's own
find/lower_bound member, or just convert.
std::merge, std::set_union, std::set_intersection, std::set_difference,
std::set_symmetric_difference, std::includes.
// Merge two time-sorted sensor streams into one timeline.
std::vector<Sample> imu, encoder, fused;
fused.reserve(imu.size() + encoder.size());
std::merge(imu.begin(), imu.end(),
encoder.begin(), encoder.end(),
std::back_inserter(fused),
[](const auto& a, const auto& b){ return a.t < b.t; });make_heap, push_heap, pop_heap, sort_heap, is_heap, is_heap_until.
By default these form a max-heap (largest at front). Pass
std::greater<>{} for a min-heap. (Again: greater_equal is UB.)
std::vector<int> v = {6, 10, 7, 17, 10, 15};
std::make_heap(v.begin(), v.end()); // max-heap
v.push_back(100); std::push_heap(v.begin(), v.end());
std::pop_heap(v.begin(), v.end()); v.pop_back(); // remove the max
std::sort_heap(v.begin(), v.end()); // ascending; no longer a heapstruct Cell { int idx; float cost; };
struct ByCost {
bool operator()(const Cell& a, const Cell& b) const {
return a.cost > b.cost; // min-heap on cost
}
};
std::priority_queue<Cell, std::vector<Cell>, ByCost> open;
open.push({start_idx, 0.f});Lambda comparator (the type is unnamed, so decltype):
auto by_cost = [](const Cell& a, const Cell& b){ return a.cost > b.cost; };
std::priority_queue<Cell, std::vector<Cell>, decltype(by_cost)> open(by_cost);Or, easiest of all, give Cell an operator< and use the default:
struct Cell {
int idx; float cost;
bool operator<(const Cell& o) const { return cost < o.cost; } // max-heap on cost
};
std::priority_queue<Cell> q;| Function | Notes |
|---|---|
min, max, minmax |
Scalar or initializer-list overloads. minmax returns a pair. |
min_element, max_element, minmax_element |
Iterator versions. |
clamp (C++17) |
clamp(v, lo, hi) — saturates v into [lo, hi]. |
// Saturate a motor command before sending it on the CAN bus.
double cmd = std::clamp(controller_output, -MAX_TORQUE, MAX_TORQUE);Lifetime trap: std::min/std::max return references. Don't bind to
auto&/const auto& for an initializer_list overload — the temporaries die
at the semicolon.
const auto& m = std::min({a, b, c}); // dangling — DON'T
auto m = std::min({a, b, c}); // finestd::equal— element-wise equality of two ranges.std::lexicographical_compare— dictionary order; also has a_three_wayvariant (C++20) that returns astd::strong_ordering.
std::next_permutation,std::prev_permutation— iterate permutations lexicographically.std::shuffle(first, last, urbg)— uniform random shuffle. Pass a real generator likestd::mt19937{std::random_device{}()}.std::random_shufflewas deprecated in C++14 and removed in C++17.std::sample(first, last, out, k, urbg)— samplekelements without replacement (C++17).
// Random downsample of a dense lidar cloud, no duplicates.
std::vector<Point> sparse;
sparse.reserve(2000);
std::sample(cloud.begin(), cloud.end(),
std::back_inserter(sparse), 2000,
std::mt19937{std::random_device{}()});| Function | Notes |
|---|---|
iota |
Fill with successive values starting from v0. |
accumulate |
Left fold, sequential, requires associativity for sane reordering. |
reduce (C++17) |
Like accumulate but allowed to reorder — needs commutative+associative op. Accepts execution policies. |
transform_reduce |
Map-then-reduce in one pass. |
inner_product |
Generalized dot product. |
partial_sum, inclusive_scan, exclusive_scan |
Prefix sums; the scans accept execution policies. |
adjacent_difference |
out[i] = in[i] - in[i-1]. |
gcd, lcm (C++17) |
|
midpoint (C++20) |
Overflow-safe midpoint. |
ranges::fold_left, ranges::fold_right (C++23) |
Proper left/right fold; can return a different type than the elements. |
std::vector<double> x(N);
std::iota(x.begin(), x.end(), 0.0); // 0, 1, 2, ..., N-1
double sum = std::reduce(std::execution::par, x.begin(), x.end(), 0.0);
double sumsq = std::transform_reduce(x.begin(), x.end(), 0.0,
std::plus<>{},
[](double v){ return v*v; });C++23 fold_left is preferred over accumulate when the accumulator type
differs from the element type — it handles that without surprising decay:
auto labels = scan_labels(); // std::vector<std::string_view>
auto joined = std::ranges::fold_left( // C++23
labels, std::string{},
[](std::string acc, std::string_view s){ acc += s; return acc; });Every classic algorithm has a std::ranges:: counterpart that:
- Takes a range directly (no
begin()/end()boilerplate). - Supports projections — a function applied to each element before the predicate or comparator sees it.
- Returns richer result types (
ranges::*_resultaggregates) and Sentinel-aware iterators.
struct Robot { std::string name; double battery; };
std::vector<Robot> fleet = ...;
// Sort by battery descending — no comparator needed, just a projection.
std::ranges::sort(fleet, std::greater<>{}, &Robot::battery);
// First robot below 20% — same pattern.
auto low = std::ranges::find_if(fleet,
[](double b){ return b < 0.2; },
&Robot::battery);Common counterparts you'll reach for:
ranges::sort, ranges::stable_sort, ranges::find, ranges::find_if,
ranges::count_if, ranges::transform, ranges::copy, ranges::copy_if,
ranges::min_element, ranges::max_element, ranges::for_each,
ranges::any_of, ranges::all_of, ranges::none_of,
ranges::unique, ranges::partition, ranges::merge,
ranges::lower_bound, ranges::binary_search.
C++23 additions: ranges::contains, ranges::contains_subrange,
ranges::starts_with, ranges::ends_with, ranges::fold_left,
ranges::fold_right, ranges::find_last, ranges::find_last_if.
std::vector<int> in = {1,2,3,4}, out;
std::ranges::transform(in, std::back_inserter(out),
[](int x){ return x * 2; });Views are non-owning, lazily evaluated adaptors that you compose with |.
They never materialize an intermediate container. Perfect for streaming
sensor data.
namespace rv = std::views;
auto fresh = readings
| rv::filter([](const Reading& r){ return r.valid; })
| rv::transform(&Reading::value)
| rv::take(100);
for (double v : fresh) { /* ... */ }Useful views:
- C++20:
filter,transform,take,take_while,drop,drop_while,reverse,keys,values,elements,split,join,common,iota,single,empty. - C++23:
zip,zip_transform,enumerate,adjacent<N>,slide,chunk,chunk_by,stride,join_with,as_const,as_rvalue,cartesian_product,repeat.
// C++23: walk pairs of consecutive joint positions to compute velocities.
auto vel = std::views::zip(positions, positions | std::views::drop(1))
| std::views::transform([dt](auto p){
auto [a, b] = p; return (b - a) / dt;
});
// C++23: index your samples without a manual counter.
for (auto [i, sample] : std::views::enumerate(samples)) { /* ... */ }
// C++23: process the scan in 16-point windows.
for (auto window : scan | std::views::chunk(16)) { /* ... */ }auto positives = readings
| std::views::transform(&Reading::value)
| std::views::filter([](double v){ return v > 0; })
| std::ranges::to<std::vector>(); // C++23Before C++23, use std::ranges::copy(view, std::back_inserter(vec)) instead.
Most numeric and many <algorithm> functions accept an execution policy as the
first argument:
#include <execution>
std::sort(std::execution::par_unseq, v.begin(), v.end());
double s = std::reduce(std::execution::par, v.begin(), v.end(), 0.0);Policies:
seq— sequential, well-defined order.par— may run on multiple threads; element-access must be thread-safe.par_unseq— may also vectorize; your callable must not take locks, callnew/delete, or anything that can't be interleaved.unseq(C++20) — vectorization without threading.
See execution_policies.md for the longer write-up. On
Linux with libstdc++, parallel policies require linking against TBB
(-ltbb).
- Strict weak ordering.
sort,nth_element,make_heap,priority_queuecomparators must returnfalsefor equal elements.greater_equal/less_equalare UB even if your test cases happen to pass. - Sorted-range preconditions.
binary_search,lower_bound,merge,set_*,includesall assume sorted input under the same comparator you pass. removedoes not shrink. Pair witherase, or just usestd::erase/std::erase_if(C++20).for_eachdiscards return values from the callable.min/maxreturn references. Don't bind toconst auto&for theinitializer_listoverload — the temporaries die at the semicolon.- Iterator categories.
sort,nth_element,shufflerequire random-access iterators — they won't compile onstd::list(uselist::sort). - Container size changes. Algorithms operate on iterator ranges; they
never insert. Use
back_inserter/inserterfor outputs that need to grow. - Dangling views. Views are non-owning. Never return a view that adapts a local container.