STL Containers and Algorithms
STL Containers and Algorithms
Combine containers, iterators, and algorithms to write expressive code with less manual looping.
STL Containers and Algorithms
Combine containers, iterators, and algorithms to write expressive code with less manual looping.
std::vectorstd::vector<int> values{5, 2, 8, 1};
std::sort(values.begin(), values.end());
auto it = std::find(values.begin(), values.end(), 8);
auto zeros = std::count(values.begin(), values.end(), 0);
auto passing = std::count_if(values.begin(), values.end(), [](int value) {
return value >= 50;
});
find_if and count_if are often the first algorithms you reach for once the condition depends on more than simple equality.
std::vector<int> doubled(values.size());
std::transform(values.begin(), values.end(), doubled.begin(),
[](int x) { return x * 2; });
Algorithms shine when the operation is clearer than a manual loop. You describe the transformation instead of writing the loop mechanics again.
Think in terms of operations on a range, not always in terms of manual index loops.
std::vector<int> scores{91, 84, 76, 99, 88};
std::vector<int> passing;
std::copy_if(scores.begin(), scores.end(), std::back_inserter(passing),
[](int score) { return score >= 80; });
int total = std::accumulate(passing.begin(), passing.end(), 0);
This is a realistic pattern: select a subset, then compute something over it.
struct Record {
std::string name;
int score{};
};
std::vector<Record> records{{"Ada", 91}, {"Bjarne", 78}, {"Grace", 95}};
std::vector<std::string> top_names;
std::for_each(records.begin(), records.end(), [&](const Record& record) {
if (record.score >= 90) {
top_names.push_back(record.name);
}
});
std::ranges::sort(top_names);
This combines selection, transformation, and final ordering. In real code, algorithms often appear in short sequences rather than one isolated call at a time.
values.erase(std::remove(values.begin(), values.end(), 0), values.end());
Older STL algorithms often rearrange elements but do not actually shrink the container. Learn this pattern early.
std::erase(values, 0);
In newer standard library code, prefer std::erase or std::erase_if when available because they express the intent directly.
std::ranges::sort(values);
auto found = std::ranges::find(values, 8);
The ranges overloads are easier to read because they work directly with containers.
struct User {
std::string name;
int score{};
};
std::vector<User> users{{"Ada", 91}, {"Bjarne", 88}, {"Grace", 99}};
std::ranges::sort(users, std::greater<>{}, &User::score);
Projections let you target a member directly without building a new comparator every time.
std::find_if: find the first element matching a predicatestd::partition: group values by a conditionstd::accumulate: combine a range into one resultstd::all_of, std::any_of, std::none_of: express checks directlystd::rotate: reorder elements without handwritten index jugglingWhen sorting objects, you can project on a member instead of writing a custom comparator for every case.
remove does not erase from the container by itselffind, count_if, transform, or sort would be clearerWhen the loop is really about searching, filtering, transforming, or checking a condition, look for an algorithm first. When the loop is maintaining a more complex invariant across several moving parts, a normal loop may still be clearer.
#include <algorithm>
#include <vector>
int main() {
std::vector<int> values{4, 1, 3, 2};
std::sort(values.begin(), values.end());
return std::binary_search(values.begin(), values.end(), 3) ? 0 : 1;
}
Replace the hand-written search loop with a ranges pipeline. It highlights when composition improves clarity and when it becomes too indirect.
#include <ranges>
#include <vector>
int main() {
std::vector<int> values{1, 2, 3, 4, 5, 6};
auto odd = values | std::views::filter([](int value) { return value % 2 == 1; });
return *odd.begin();
}