3 Function objects
This chapter surveys everything C++ can treat as a “callable,” showing how to use higher-level, functional-style abstractions without sacrificing performance. It distinguishes ordinary functions from function objects (anything invocable with f(args...)), covering function pointers/references, classes that overload operator(), and template-based, generic function objects. Practical guidance includes return type options (trailing types, automatic deduction, decltype(auto)), plus correct argument/result forwarding with forwarding references and std::forward to preserve value categories and avoid unnecessary copies.
A large part of the chapter centers on lambdas as concise, inline function objects. It explains capture lists (by value, by reference, mixed and default captures), the fact that lambda call operators are const by default and when to use mutable, and what compilers generate under the hood (a closure class with captured state and an operator()). It also introduces generalized lambda captures for creating or moving state (e.g., move-only types like unique_ptr) directly into the closure, and generic lambdas whose parameters use auto so a single lambda can adapt to many argument types, enabling succinct, local definitions for algorithms and higher-order utilities.
For terser and reusable predicates, the chapter demonstrates hand-rolled function objects (e.g., an error-testing predicate with convenient operators), the standard library’s operator wrappers (plus, multiplies, greater, etc.), and expression-style composition with placeholder-based libraries such as Boost.Phoenix—along with the trade-off of slower compile times. Finally, it covers std::function as a type-erased wrapper that can hold any callable matching a given signature (including lambdas, functors, function pointers, and even member pointers), noting its convenience across interfaces and separate compilation units but also its runtime costs (type erasure, potential dynamic allocation) and constness caveats. The overarching advice is to favor concrete functors, lambdas, and templates for optimal performance, reserving std::function for when explicit, uniform callable types are required.
You can use an ordinary function that checks whether a person is more than 42 years old when you want to count the number of people over that age limit. But with this approach, you have the ability to check the age against a single predefined value.
When creating the predicate, it’s useful not only to allow the specification of different age limits, but also to support different types that have the age information.
Getting all females from a group of people. You’ve been using a predicate called is_female, which is a non-member function for this. Now you’re going to do the same with a lambda.
You’re passing a lambda as a predicate to the std::count_if algorithm. The lambda receives a single employee and checks whether a person belongs to the team you’re checking the count for. The std::count_if algorithm will count all the yeses and return the answer.
You’re filtering the responses based on whether they contain an error, in order to process them separately.
The Phoenix expression is composed of captured values and placeholders that represent the function arguments. When the arguments are provided by calling the function object, the expression is evaluated and the calculated value returned as the result of the function.
Summary
- It’s possible to use objects that are castable to function pointers as if they were ordinary functions, but the call operator is the preferred way of creating function objects.
- For functions whose return type isn’t important enough to be explicitly stated, you can let the compiler deduce it from the value you’re returning by using the
autokeyword. - Using the automatic return type deduction avoids any type of conversion or narrowing that might arise when you specify the type explicitly (for example, returning a
doublevalue in a function that should return anint). - If you want to create a function object that can work on a multitude of types, you should make its call operator a function template.
- Lambdas are useful syntactic sugar for creating function objects. Using them is usually better than writing whole classes with call operators by hand.
- Lambdas in C++14 have become a true replacement for most function objects that you could write by hand. They add new possibilities when capturing variables, and they support creating generic function objects.
- Although the lambdas provide terser syntax for creating function objects, they’re still overly verbose for some cases. In those situations, you can use the libraries such as Boost.Phoenix, or roll out your own function objects.
- The
std::functionis a useful, but it comes with a performance penalty equivalent to a virtual function call.
Functional Programming in C++ ebook for free