Skip to content

← Containers, Strings, Algorithms step 4 of 5

Medium Primitives

The loop stops having bugs once it has a name

The argument for <algorithm> is usually made about brevity, and brevity is the least of it. The real argument is that a hand-written loop has places for bugs to live and a named algorithm does not.

int best = 0;                       // <- here
for (std::size_t i = 0; i < values.size(); ++i) {   // <- and here
    if (values[i] > best) {         // <- and here
        best = values[i];
    }
}

Three lines, three classic defects: the accumulator initialised to a value that is not in the data, the index arithmetic, and the comparison that decides ties. std::ranges::max_element(values) has none of them, and it also forces you to answer the question the loop above silently got wrong — what is the maximum of an empty range? — because it hands you end() and makes you say.

The ones worth knowing by name

max_element / min_element an iterator to the largest / smallest
find / find_if the first match, or end()
count / count_if how many
any_of / all_of / none_of a question, answered as a bool
accumulate (in <numeric>) a fold
sort / stable_sort / partial_sort ordering, with a choice about ties
transform element-wise mapping
remove_if + erase filtering in place — Track 5.5

Each of these turns a shape you would have to read into a word you can recognise. A reviewer skimming std::ranges::any_of(users, is_admin) is done in a second; the same thing spelled as a loop with an early return true needs actual reading.

Two forms

C++20 added range overloads that take the container itself:

std::ranges::max_element(values);                       // preferred
std::max_element(values.begin(), values.end());         // the older form

Same algorithm. The range form cannot be given two iterators from different containers, which is a real class of bug removed. clang-tidy’s modernize-use-ranges will ask you for it, and the gate enforces that.

A few things still have no range form in C++20 — std::accumulate is the one you will meet first — so the iterator pair is not obsolete.

accumulate and its initial value

std::accumulate(v.begin(), v.end(), 0);     // sums as int
std::accumulate(v.begin(), v.end(), 0LL);   // sums as long long

The accumulator’s type comes from the initial value, not from the elements. A vector of int summed with 0 overflows exactly as a plain int would, which is a bug you cannot see at the call site unless you know this rule.

Your task

std::vector<long long> summarize(const std::vector<int>& values, int threshold);

Return four numbers, in this order:

  1. the largest value, or 0 if values is empty;
  2. the index of the first value strictly greater than threshold, or -1 if there is none;
  3. how many values are strictly greater than threshold;
  4. the sum of every value, as a long long.

The starter does all four by hand and gets two of them wrong. Both mistakes are the kind that a named algorithm does not have room for.