Use Named Predicates to improve readability when using algorithms
Named predicates like is_positive can improve readability and intent when working with generic algorithms, by reducing the boilerplate of lambda syntax.
const auto is_positive = [](const int value) { return value > 0; };Higher-order functions like greater_than add more power to these named predicates by enabling input arguments to the predicates.
template <typename T>auto greater_than(const T& other) { return [other](const T& element) { return element > other; };}Named predicates and higher order functions reduce boilerplate and keep code expressive. For more advanced or type-specific predicates, functors offer greater flexibility, especially when template specialization is needed.
// Define `equals` higher order functiontemplate <typename T> auto equals(const T &other) { return [other](const T &element) { return element == other; };
// Usage with standard algorithmbool found = std::any_of( numbers.cbegin(), numbers.cend(), equals(3));
// Usage with C++20 Rangesbool found = std::ranges::any_of(numbers, equals(3));Notice how closely the C++ code resembles the natural expression: any of numbers equals (to) 3. Compared with a raw for-loop or for-each loop, this is much more concise and expressive. It is not concerned with verbose implementation or syntax details.
Functors For Flexibility
Section titled “Functors For Flexibility”template <typename T>auto has_id(int id) { return [id](const T& element) { return element.id == id; };}
auto object_42 = std::find_if( objects.cbegin(), objects.cend(), has_id<Object>(42));The previous snippet requires an explicit template parameter Object in has_id<Object>.
This is boilerplate that distracts from the expression of intent.
The need for this can be eliminated by using a functor instead of a lambda.
struct has_id { has_id(int id) : id(id) {}
template<typename T> bool operator()(const T& obj) { return obj.id == id; }
private: int id;};
struct Object { int id; };
// has_id without template argumentauto object_42 = std::ranges::find_if(objects, has_id(42));Using a functor, we can also extend and specialize. In the following snippet, we extend has_id with a specialization for Widget, which provides access to its the id via the get_id() method.
class Widget {public: Widget(int id) : id(id) {} int get_id() const { return id; }private: int id;};
template <>bool has_id::operator()(const Widget& obj) { return obj.get_id() == id;}Pseudocode as expression of intent
is any of the numbers positive?Messy C++
bool found = false;for(int x : numbers) { if (x > 0) { found = true; break; }}Better
bool found = std::any_of( numbers.cbegin(), numbers.cend(), [](const int x) { return x > 0; });}Best
using std::ranges::any_of;bool is_any_positive = any_of(numbers, is_positive);// Or:bool is_any_positive = any_of(numbers, greater_than(0));Guidelines
Section titled “Guidelines”- Prefer named predicates over inlining lambdas directly in algorithm calls. This makes code more concise and improves readability.
- Use higher-order functions to generate predicates that take input arguments, such as
equals(3)orgreater_than(42). - Reach for functors when you need extensibility or type-specific behavior via specialization.
- Build a reusable predicate library in your project, so common checks can be defined once and reused, without duplicating boilerplate at the call sites.