Pass std algos predicates by reference in C
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
C++ standard library algorithms like std::sort, std::find_if, and std::for_each accept predicates (callable objects) by value by default. This means the algorithm copies your predicate, which is usually fine for lambdas and function pointers but can be problematic for stateful functors. To pass predicates by reference, use std::ref or template wrappers.
The Default: Predicates Are Copied
The original counter object shows count = 0 because the algorithm worked on a copy. The copy is discarded when the algorithm returns.
Fix 1: Use std::ref
std::ref creates a reference wrapper that the algorithm copies, but the wrapper refers to the original object:
std::ref(counter) creates a std::reference_wrapper<Counter> that forwards operator() calls to the original counter object.
Fix 2: Use a Lambda That Captures by Reference
Lambdas capturing by reference ([&]) are lightweight and do not suffer from the copy problem because the lambda itself is small (just a pointer to the captured variables), and even when copied, all copies point to the same captured variables.
Fix 3: Retrieve the Functor from the Algorithm
Some algorithms return the functor after use. std::for_each is the notable example:
This works because std::for_each returns its functor parameter by value after processing. Most other algorithms (like std::sort, std::count_if) do not return the predicate.
When Does Copying Matter?
Stateless Predicates — Copying Is Fine
Stateful Predicates — Copying Loses State
Expensive-to-Copy Predicates
std::ref with Different Algorithms
Why Algorithms Copy Predicates
The C++ standard specifies that algorithms take predicates by value because:
- Algorithms may copy internally: Some algorithms partition work across multiple passes or call the predicate from different internal functions
- Thread safety: Copies avoid data races in parallel algorithms (
std::execution::par) - Simplicity: Value semantics are simpler for the common case (stateless predicates)
The standard explicitly allows algorithms to copy predicates an unspecified number of times, which is why stateful predicates require std::ref.
Parallel Algorithms and Predicates
With C++17 parallel algorithms, passing stateful predicates by reference requires thread safety:
Use std::atomic or std::mutex for shared state in parallel algorithms.
Common Pitfalls
- Lost state: The most common mistake is expecting a stateful functor to retain modifications after being passed to an algorithm by value. The algorithm operates on a copy, and the original is unchanged.
- std::ref lifetime: The object referenced by
std::refmust outlive the algorithm call. Passingstd::refto a temporary or a local that goes out of scope causes undefined behavior. - Predicate purity assumption: The standard assumes predicates do not modify the elements they are called with. Predicates that mutate elements cause undefined behavior with most algorithms.
- Parallel safety: Using
std::refwithstd::execution::parrequires the predicate to be thread-safe. Unsynchronized mutations to shared state cause data races. - std::cref for const: Use
std::crefwhen the predicate should not be modified (const reference). This prevents accidental mutation of the original object.
Summary
- C++ standard algorithms copy predicates by value — stateful functors lose their state
- Use
std::ref(predicate)to pass by reference and preserve state mutations - Lambdas capturing by reference (
[&]) naturally avoid the copy problem std::for_eachreturns the functor, allowing state retrieval withoutstd::ref- For parallel algorithms, ensure thread safety when using
std::refwith stateful predicates

