Function Objects (Functors)
Learn function objects (functors) - classes that act like functions and provide state and customization
Discover function objects (functors) - classes that behave like functions but can maintain state and be customized.
A Simple Example
#include <iostream>
#include <algorithm>
#include <vector>
// Simple functor
struct Multiplier {
int factor;
Multiplier(int f) : factor{f} {}
int operator()(int x) const {
return x * factor;
}
};
// Predicate functor
struct IsEven {
bool operator()(int x) const {
return x % 2 == 0;
}
};
// Stateful functor
class Counter {
private:
mutable int count{0}; // mutable allows modification in const operator()
public:
void operator()(int) const {
count++;
}
int getCount() const {
return count;
}
};
int main() {
std::vector<int> numbers{1, 2, 3, 4, 5};
// Use functor with transform
Multiplier times3{3};
std::transform(numbers.begin(), numbers.end(), numbers.begin(), times3);
std::cout << "After multiply by 3: ";
for (int n : numbers) std::cout << n << " ";
std::cout << "\n";
// Use predicate functor
int evenCount = std::count_if(numbers.begin(), numbers.end(), IsEven{});
std::cout << "Even numbers: " << evenCount << "\n";
// Stateful functor
Counter counter;
std::for_each(numbers.begin(), numbers.end(), std::ref(counter));
std::cout << "Processed " << counter.getCount() << " items" << "\n";
return 0;
}
Breaking It Down
What is a Functor?
-
What it is: A class that overloads
operator()to make objects callable like functions -
Syntax: Define
operator()as a member function with any parameters and return type -
Usage: Call it like a function -
myFunctor(args)invokesoperator()(args) - Remember: Functors are objects that act like functions
Functors vs Lambdas
- Lambdas are anonymous functors: The compiler generates a functor class behind the scenes
- Functors have names: Better error messages and can be forward-declared
- Functors are testable: Write unit tests for functor classes
- Remember: Choose lambdas for convenience, functors for reusability and testing
Stateful Functors
- What they do: Maintain state across multiple calls
- Example: Count elements, accumulate sums, track statistics
-
Caveat: STL algorithms copy functors - use
std::ref()to preserve state -
Remember: Mark
operator()const and usemutablefor state that changes
Predicate Functors
- What they are: Functors that return bool, used for filtering and searching
-
Use with:
std::find_if,std::count_if,std::remove_if, etc. - Benefit: More readable than inline lambdas for complex conditions
-
Remember: Make
operator()const since predicates shouldn't modify state
Why This Matters
- While lambdas are convenient for simple cases, functors offer advantages: named types, testability, default constructibility, and the ability to have complex state and multiple member functions.
- Understanding functors deepens your knowledge of how the STL works and gives you more tools for different situations.
Critical Insight
Functors are what lambdas become behind the scenes! When you write a lambda, the compiler generates an anonymous functor class with operator() and captured variables as members. Understanding functors means you understand what lambdas really are.
Functors have advantages: they're testable (you can write unit tests for the functor class), they have a name (better error messages), and they can have more complex state and behavior with multiple member functions.
Choose lambdas for convenience and one-off uses. Choose functors when you need a reusable, testable component that might be used in multiple places.
Best Practices
Make operator() const: If your functor doesn't modify member variables, mark operator() const for broader usability.
Use descriptive names: Unlike lambdas, functors have names - use them to document intent (IsEven, Multiplier).
Consider testability: Functors can be unit tested independently, making them better for complex logic.
Use std::ref for stateful functors: Wrap in std::ref() when passing to algorithms if you need to preserve state.
Common Mistakes
Forgetting const on operator(): If your functor doesn't modify state, make operator() const. Many STL algorithms require const-callable functors.
Not using std::ref with stateful functors: When passing functor to algorithm, it's copied. Use std::ref(functor) to preserve state.
Expecting state changes: Without std::ref, algorithms work on a copy - state changes are lost.
Complex functors vs lambdas: For simple one-off operations, lambdas are clearer. Use functors when you need reusability or testing.
Debug Challenge
This functor won't compile because operator() isn't callable. Click the highlighted line to fix it:
Quick Quiz
- What's the main advantage of functors over lambdas?
- When are functors copied by STL algorithms?
- What makes a class a functor?
Practice Playground
Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.
Output:
Error:
Lesson Progress
- Fix This Code
- Quick Quiz
- Practice Playground - run once