STL
algorithms
predicates
C++
container

What STL algorithm can determine if exactly one item in a container satisfies a predicate?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In C++, the simplest way to check whether exactly one element matches a predicate is std::count_if combined with a comparison to one. This is clear and correct for most codebases. For large ranges, you can also use a short-circuit approach that stops once a second match is found.

Direct Solution with std::count_if

std::count_if scans the range and returns how many elements satisfy a predicate.

cpp
1#include <algorithm>
2#include <iostream>
3#include <vector>
4
5int main() {
6    std::vector<int> values{2, 4, 7, 8, 10};
7
8    auto is_odd = [](int x) { return x % 2 != 0; };
9    bool exactly_one = std::count_if(values.begin(), values.end(), is_odd) == 1;
10
11    std::cout << std::boolalpha << exactly_one << "
12";
13}

This is expressive and easy to read in code reviews.

C++20 Ranges Variant

If you use ranges, the same intent is even cleaner.

cpp
1#include <algorithm>
2#include <iostream>
3#include <ranges>
4#include <vector>
5
6int main() {
7    std::vector<int> data{3, 6, 9, 12};
8    bool exactly_one_multiple_of_five =
9        std::ranges::count_if(data, [](int x) { return x % 5 == 0; }) == 1;
10
11    std::cout << std::boolalpha << exactly_one_multiple_of_five << "
12";
13}

Choose this when your project already uses ranges heavily.

Early-Exit Approach for Performance-Sensitive Paths

count_if always scans the entire range. If matches are common and containers are large, you can stop early after finding a second match.

cpp
1#include <algorithm>
2#include <iostream>
3#include <vector>
4
5bool exactly_one_even(const std::vector<int>& v) {
6    auto first = std::find_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; });
7    if (first == v.end()) {
8        return false;
9    }
10
11    auto second = std::find_if(std::next(first), v.end(), [](int x) { return x % 2 == 0; });
12    return second == v.end();
13}
14
15int main() {
16    std::vector<int> a{1, 3, 5, 8};
17    std::vector<int> b{2, 3, 4, 5};
18
19    std::cout << std::boolalpha << exactly_one_even(a) << "
20";
21    std::cout << std::boolalpha << exactly_one_even(b) << "
22";
23}

This can reduce work significantly when second matches appear early.

Generic Helper for Reuse

If the check appears often, wrap it in a utility helper to make intent obvious.

cpp
1#include <algorithm>
2#include <iterator>
3
4template <typename It, typename Pred>
5bool exactly_one_of(It first, It last, Pred pred) {
6    int matches = 0;
7    for (; first != last; ++first) {
8        if (pred(*first)) {
9            ++matches;
10            if (matches > 1) {
11                return false;
12            }
13        }
14    }
15    return matches == 1;
16}

This keeps call sites concise and allows early exit.

Choosing Readability Versus Micro-Optimization

For most business applications, count_if == 1 is the best default. It is immediately understandable and less error-prone than custom loop logic.

Use early-exit helpers only when profiling shows this predicate check is on a hot path. Premature optimization can make code harder to maintain without measurable benefit.

If predicates are expensive, consider caching or precomputing classification values upstream rather than optimizing the matching loop alone.

Testing Strategy

Add tests for the three essential cases:

  • No matches.
  • Exactly one match.
  • More than one match.

Also test empty containers and edge values. These tests prevent regression when predicates or data structures evolve.

Common Pitfalls

A common pitfall is using std::any_of and assuming it means exactly one. any_of checks one or more, not exactly one.

Another issue is writing custom loops without early-exit guard and accidentally counting all matches anyway. If you need performance, ensure loop logic returns as soon as the result is determined.

Developers also forget that predicate side effects can make behavior order-dependent. Keep predicates pure whenever possible.

Finally, mixing signed and unsigned counters in custom helpers can introduce subtle warnings or bugs. Use consistent types in reusable utilities.

Summary

  • The standard answer is std::count_if(...) == 1.
  • std::ranges::count_if offers the same pattern in modern C++.
  • Use early-exit logic only when profiling justifies it in real workloads.
  • Prefer pure predicates and explicit tests for zero, one, and many matches.
  • Optimize for readability first, then tune proven hot paths.

Course illustration
Course illustration

All Rights Reserved.