Pass std algos predicates by reference in C
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
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
Related reading
- Passing function objects into std algorithms by reference
- path compression is enough for disjoint-set forests , why do we need union by rank
- Path finding Algorithms A Vs Jump Point Search
- Pathfinding on large map
- Performance consequence of volatile member functions
- Performance impact of realloc
- Pathfinding routing, trip planning, ... algorithms on graphs with time restrictions
- paxos algorithm - how does the propose stage work?

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.