Function Pointers, Function Objects, and Lambda Expressions
Functions in C++ are first-class functions, as functions can be used in the same way as normal variables, such as passing them as arguments to other functions, returning them from other functions, and assigning them to variables. A term that often comes up in this context is a callback, representing something that can be called. It can be a function pointer or something that behaves like a function pointer, such as an object with an overloaded operator(), or an inline lambda expression. A class that overloads operator() is called a function object, or functor for short. Conveniently, the Standard Library provides a set of classes that can be used to create callback objects and to adapt existing callback objects. Lambda expressions allow you to create small inline callbacks right at the place where you need them, which improves the readability and maintainability of your code. It’s time to take a closer look at the concept of callbacks, because many of the algorithms explained in the next chapter accept such callbacks to customize their behavior.
FUNCTION POINTERS
Section titled “FUNCTION POINTERS”You don’t normally think about the location of functions in memory, but each function actually lives at a particular address. In C++, you can use functions as data; that is, C++ has first-class functions. In other words, you can take the address of a function and use it like you use a variable.
Function pointers are typed according to the parameter types and return type of compatible functions. Here’s an example of a definition for a variable called fun capable of pointing to functions returning a Boolean and accepting two int arguments:
bool (*fun)(int, int);Don’t forget the parentheses around *fun; otherwise, this statement would not be a variable but a function prototype for a function named fun, accepting two ints and returning a pointer to a bool. The fun function pointer is uninitialized. As you know, uninitialized data should be avoided. You could initialize fun to nullptr as follows:
bool (*fun)(int, int) { nullptr };findMatches() Using Function Pointers
Section titled “findMatches() Using Function Pointers”Another way to work with function pointers is to use type aliases. A type alias allows you to assign a type name to the family of functions that have the given characteristics. For example, the following defines a type called Matcher representing a pointer to any function that has two int parameters and returns a bool:
using Matcher = bool(*)(int, int);The following type alias defines a type called MatchHandler for functions accepting a size_t and two ints and returning nothing:
using MatchHandler = void(*)(size_t, int, int);Now that these types are defined, you can write a function that takes two callbacks as parameters: a Matcher and a MatchHandler. Functions that accept other functions as parameters, or functions that return a function are called higher-order functions. For example, the following function accepts two spans of integers, as well as a Matcher and MatchHandler. It iterates through the spans in parallel and calls the Matcher on corresponding elements of both spans. If the Matcher returns true, the MatchHandler is called with as first argument the position of the match, and as second and third arguments the values that caused the Matcher to return true. Notice that even though the Matcher and MatchHandler are passed in as variables, they can be called just like regular functions:
void findMatches(span<const int> values1, span<const int> values2, Matcher matcher, MatchHandler handler){ if (values1.size() != values2.size()) { return; } // Must be same size.
for (size_t i { 0 }; i < values1.size(); ++i) { if (matcher(values1[i], values2[i])) { handler(i, values1[i], values2[i]); } }}Note that this implementation requires that both spans have the same number of elements. To call the findMatches() function, all you need is any function that adheres to the defined Matcher type—that is, any function that takes in two ints and returns a bool—and a function that adheres to the MatchHandler type. Here is an example of a possible Matcher, returning true if the two parameters are equal:
bool intEqual(int value1, int value2) { return value1 == value2; }The following is an example of a MatchHandler that simply prints out the match:
void printMatch(size_t position, int value1, int value2){ println("Match found at position {} ({}, {})", position, value1, value2);}The intEqual() and printMatch() functions can be passed as arguments to findMatches(), as follows:
vector values1 { 2, 5, 6, 9, 10, 1, 1 };vector values2 { 4, 4, 2, 9, 0, 3, 1 };println("Calling findMatches() using intEqual():");findMatches(values1, values2, &intEqual, &printMatch);The callback functions are passed to findMatches() by taking their addresses. Technically, the & is optional—if you omit it and only put the function name, the compiler will know that you mean to take its address. The output is as follows:
Calling findMatches() using intEqual():Match found at position 3 (9, 9)Match found at position 6 (1, 1)The benefit of function pointers lies in the fact that findMatches() is a generic function that compares parallel values in two vectors. As it is used in the previous example, it compares values based on equality. However, because it takes a function pointer, it could compare values based on other criteria. For example, the following function also adheres to the definition of Matcher:
bool bothOdd(int value1, int value2) { return value1 % 2 == 1 && value2 % 2 == 1; }The following code snippet shows that bothOdd() can also be used in a call to findMatches():
println("Calling findMatches() using bothOdd():");findMatches(values1, values2, bothOdd, printMatch);The output is as follows:
Calling findMatches() using bothOdd():Match found at position 3 (9, 9)Match found at position 5 (1, 3)Match found at position 6 (1, 1)By using function pointers, a single function, findMatches(), can be customized to different uses based on functions/callbacks passed in as arguments.
findMatches() As a Function Template
Section titled “findMatches() As a Function Template”You don’t need to use explicit function pointer parameters for findMatches() to accept callback parameters. Instead, you can convert findMatches() to a function template. The only changes needed are to remove the Matcher and MatchHandler type aliases and to make findMatches() a function template. The changes are highlighted:
template <typename Matcher, typename MatchHandler>void findMatches(span<const int> values1, span<const int> values2, Matcher matcher, MatchHandler handler){ /* … */ }Better yet is the following constrained function template. The Matcher template type parameter is constrained (see Chapter 12, “Writing Generic Code with Templates”) with predicate<int,int> to make sure the user supplies a callback that can be called with two int arguments and returns a Boolean. Similarly, the MatchHandler template type parameter is constrained to callbacks that can be called with one size_t argument and two int arguments and returns nothing.
template <predicate<int, int> Matcher, invocable<size_t, int, int> MatchHandler>void findMatches(span<const int> values1, span<const int> values2, Matcher matcher, MatchHandler handler){ /* … */ }Both these implementations of findMatches() require two template type parameters, the type of the Matcher and MatchHandler callbacks, but thanks to function template argument deduction, calling them is the same as calling the earlier versions.
Using the abbreviated function template syntax, the findMatches() function template can be written even more elegantly as follows. Notice there is no longer an explicit template header, template<…>.
void findMatches(span<const int> values1, span<const int> values2, auto matcher, auto handler){ /* … */ }The matcher and handler parameters can again be constrained:
void findMatches(span<const int> values1, span<const int> values2, predicate<int, int> auto matcher, invocable<size_t, int, int> auto handler){ /* … */ }It should be clear by now that callbacks allow you to write very generic and configurable code. It’s exactly such use of callbacks that make many Standard Library algorithms (discussed in Chapter 20, “Mastering Standard Library Algorithms”) so powerful.
Windows DLLs and Function Pointers
Section titled “Windows DLLs and Function Pointers”One common use case for using function pointers is to obtain a pointer to a function in a dynamic link library. The following example obtains a pointer to a function in a Microsoft Windows Dynamic Link Library (DLL). A DLL is basically a library consisting of code and data that can be used by any program. An example of a specific Windows DLL is the User32 DLL, which provides, among a lot of other functionality, a function to show a message box on the screen. Details of Windows DLLs are outside the scope of this book on platform-independent C++, but it is so important to Windows programmers that it is worth discussing briefly, and it is a good example of function pointers in general.
One function in User32.dll to show a message box is called MessageBoxA(). Suppose you would like to load this library only if you need to show a message box. Loading the library at run time can be done with the Windows LoadLibraryA() function (requires <Windows.h>):
HMODULE lib { ::LoadLibraryA("User32.dll") };The result of this call is a library handle and will be NULL if there is an error. Before you can load the function from the library, you need to know the prototype for the function. The prototype for MessageBoxA() is as follows:
int MessageBoxA(HWND, LPCSTR, LPCSTR, UINT);The first parameter is the window that owns the message box (can be NULL), the second is the string to show as the message, the third is the title of the window, and the fourth is the configuration flags for the message box, such as which buttons and which icon to show.
You can now define a type alias MessageBoxFunction for a pointer to a function with the required prototype:
using MessageBoxFunction = int(*)(HWND, LPCSTR, LPCSTR, UINT);Having successfully loaded the library and defined a type alias for the function pointer, you can get a pointer to the function in the library as follows:
MessageBoxFunction messageBox { (MessageBoxFunction)::GetProcAddress(lib, "MessageBoxA") };If this fails, messageBox will be nullptr. If it succeeds, you can call the loaded function as follows. MB_OK is a flag to show only a single OK button in the message box.
messageBox(NULL, "Hello World!", "ProC++", MB_OK);POINTERS TO MEMBER FUNCTIONS (AND DATA MEMBERS)
Section titled “POINTERS TO MEMBER FUNCTIONS (AND DATA MEMBERS)”As the previous section explains, you can create and use pointers to stand-alone functions. You also know that you can work with pointers to stand-alone variables. Now, consider pointers to class data members and member functions. It’s perfectly legitimate in C++ to take the addresses of class data members and member functions in order to obtain pointers to them. However, you can’t access a non-static data member or call a non-static member function without an object. The whole point of class data members and member functions is that they exist on a per-object basis. Thus, when you want to call member functions or access data members via a pointer, you must dereference the pointer in the context of an object. Here is an example using the Employee class introduced in Chapter 1, “A Crash Course in C++ and the Standard Library”:
int (Employee::*functionPtr) () const { &Employee::getSalary };Employee employee { "John", "Doe" };println("{}", (employee.*functionPtr)());Don’t panic at the syntax. The first line declares a variable called functionPtr of type pointer to a non-static const member function of Employee that takes no arguments and returns an int. At the same time, it initializes this variable to point to the getSalary() member function of the Employee class. This syntax is quite similar to declaring a simple function pointer, except for the addition of Employee:: before the *functionPtr. Note also that the & is required in this case.
The third line calls the getSalary() member function (via the functionPtr pointer) on the employee object. Note the use of parentheses surrounding employee.*functionPtr. They are needed because operator() has higher precedence than .*.
If you have a pointer to an object, you can use ->* instead of .*, as the following code snippet demonstrates:
int (Employee::*functionPtr) () const { &Employee::getSalary };Employee johnD { "John", "Doe" };Employee* employee { &johnD };println("{}", (employee->*functionPtr)());The definition of functionPtr can be made easier to read with a type alias:
using PtrToGet = int (Employee::*) () const;PtrToGet functionPtr { &Employee::getSalary };Employee employee { "John", "Doe" };println("{}", (employee.*functionPtr)());Finally, it can be simplified even further using auto:
auto functionPtr { &Employee::getSalary };Employee employee { "John", "Doe" };println("{}", (employee.*functionPtr)());Pointers to member functions and data members won’t come up often during your day-to-day programming. However, it’s important to keep in mind that you cannot dereference a pointer to a non-static member function or data member without an object. Every so often, you may want to try something like passing a pointer to a non-static member function to a function such as qsort() that requires a function pointer, which simply won’t work.
FUNCTION OBJECTS
Section titled “FUNCTION OBJECTS”You can overload the function call operator in a class such that objects of the class can be used in place of function pointers. These objects are called function objects, or functors for short. The benefits of using a function object instead of a simple function is that a function object can keep state between calls to it.
Writing Your First Function Object
Section titled “Writing Your First Function Object”As Chapter 15, “Overloading C++ Operators,” explains, to make any class a function object, you just have to provide an overload for the function call operator. Here is a quick reminder:
class IsLargerThan{ public: explicit IsLargerThan(int value) : m_value { value } {} bool operator()(int value1, int value2) const { return value1 > m_value && value2 > m_value; } private: int m_value;};
int main(){ vector values1 { 2, 500, 6, 9, 10, 101, 1 }; vector values2 { 4, 4, 2, 9, 0, 300, 1 };
findMatches(values1, values2, IsLargerThan { 100 }, printMatch);}Note that the overloaded function call operator of the IsLargerThan class is marked as const. This is not strictly necessary in this example, but as the next chapter explains, for most Standard Library algorithms, the function call operator of predicates must be const.
operator() can be marked as static if it doesn’t require access to any non-static data members and member functions of the functor. Doing so allows the compiler to better optimize the code.
Function Objects in the Standard Library
Section titled “Function Objects in the Standard Library”Many of the Standard Library algorithms discussed in the next chapter, such as find_if(), accumulate(), and so on, accept callbacks, for example function pointers and functors, as parameters to customize the algorithm’s behavior. C++ provides several predefined functor classes, defined in <functional>, that perform the most commonly used callback operations. This section gives an overview of these predefined functors.
Your <functional> might also contain functions like bind1st(), bind2nd(), mem_fun(), mem_fun_ref(), and ptr_fun(). These functions have officially been removed since the C++17 standard and thus are not further discussed in this book. You should avoid using them.
Arithmetic Function Objects
Section titled “Arithmetic Function Objects”C++ provides functor class templates for the five binary arithmetic operators: plus, minus, multiplies, divides, and modulus. Additionally, unary negate is supplied. These classes are parametrized on the type of the operands and are wrappers for the actual operators. They take one or two parameters of the template type, perform the operation, and return the result. Here is an example using the plus class template:
plus<int> myPlus;int res { myPlus(4, 5) };println("{}", res);This example is of course silly, because there’s no reason to use the plus class template when you could just use operator+ directly. The benefit of the arithmetic function objects is that you can pass them as callbacks to other functions, which you cannot do directly with the arithmetic operators. For example, the following code snippet defines a constrained accumulateData() function template accepting an Operation as its last parameter. The implementation of geometricMean() calls accumulateData() with an instance of the predefined multiplies function object:
template <input_iterator Iter, copy_constructible StartValue, invocable<const StartValue&, const StartValue&> Operation>auto accumulateData(Iter begin, Iter end, const StartValue& startValue, Operation op){ auto accumulated { startValue }; for (Iter iter { begin }; iter != end; ++iter) { accumulated = op(accumulated, *iter); } return accumulated;}
double geometricMean(span<const int> values){ auto mult {accumulateData(cbegin(values), cend(values), 1, multiplies<int>{})}; return pow(mult, 1.0 / values.size()); // pow() is defined in <cmath>}The expression multiplies<int>{} creates a new object of the multiplies functor class template, instantiating it with type int.
The other arithmetic function objects behave similarly.
The arithmetic function objects are just wrappers around the arithmetic operators. To use them on objects of a certain type, you have to make sure that those types implement the appropriate operations, such as operator or operator+.*
Transparent Operator Functors
Section titled “Transparent Operator Functors”C++ supports transparent operator functors, which allow you to omit the template type argument. For example, you can just specify multiplies<>{}, short for multiplies<void>{}, instead of multiplies<int>{}:
double geometricMeanTransparent(span<const int> values){ auto mult { accumulateData(cbegin(values), cend(values), 1, multiplies<>{}) }; return pow(mult, 1.0 / values.size());}An important feature of these transparent operators is that they are heterogeneous. That is, they are not only more concise than the non-transparent functors, but they also have real functional advantages. For instance, the following code uses a transparent operator functor, multiplies<>{}, and uses 1.1, a double, as the start value, while the vector contains integers. accumulateData() calculates the result as a double, and result will be 6.6.
vector<int> values { 1, 2, 3 };double result {accumulateData(cbegin(values), cend(values), 1.1, multiplies<>{})};If this code uses a non-transparent operator functor, multiplies<int>{}, then accumulateData() calculates the result as an integer, and result will be 6. When you compile this code, the compiler will give you warnings about possible loss of data.
vector<int> values { 1, 2, 3 };double result { accumulateData(cbegin(values), cend(values), 1.1, multiplies<int>{}) };Finally, using transparent operators instead of the non-transparent ones can improve performance, as shown with an example in the next section.
Comparison Function Objects
Section titled “Comparison Function Objects”In addition to the arithmetic function object classes, all standard comparison operations are also available as functors: equal_to, not_equal_to, less, greater, less_equal, and greater_equal. You’ve already seen less in Chapter 18, “Standard Library Containers,” as the default comparator for elements in the priority_queue, the ordered associative containers, and the flat associative container adapters. Now you can learn how to change that criterion. Here’s an example of a priority_queue using the default comparator, std::less:
priority_queue<int> myQueue;myQueue.push(3);myQueue.push(4);myQueue.push(2);myQueue.push(1);while (!myQueue.empty()) { print("{} ", myQueue.top()); myQueue.pop();}Here is the output from the program:
4 3 2 1As you can see, the elements of the queue are removed in descending order, according to the less comparator. You can change the comparator to greater by specifying it as the comparator template type argument. The priority_queue template definition looks like this:
template <typename T, typename Container = vector<T>, typename Compare = less<T>>;Unfortunately, the Compare type parameter is last, which means that to specify it, you must also specify the container type. If you want to use a priority_queue that sorts the elements in ascending order using greater, then you need to change the definition of the priority_queue in the previous example to the following:
priority_queue<int, vector<int>, greater<>> myQueue;The output now is as follows:
1 2 3 4Note that myQueue is defined with a transparent operator, greater<>. In fact, it’s recommended to always use a transparent operator for Standard Library containers that accept a comparator type. Using a transparent comparator can be more performant compared to using a non-transparent operator. For example, if a set<string> uses a non-transparent comparator (which is the default), performing a query for a key given as a string literal causes an unwanted copy to be created, because a string instance has to be constructed from the string literal:
set<string> mySet;auto i1 { mySet.find("Key") }; // string constructed, allocates memory!//auto i2 { mySet.find("Key"sv) }; // Compilation error!When using a transparent comparator, this copying is avoided. This is called heterogeneous lookups. Here’s an example:
set<string, less<>> mySet;auto i1 { mySet.find("Key") }; // No string constructed, no memory allocated.auto i2 { mySet.find("Key"sv) }; // No string constructed, no memory allocated.Similarly, C++23 adds support for heterogeneous erasure and extraction using erase() and extract().
Unordered associative containers, such as unordered_map and unordered_set, also support transparent operators. Using a transparent operator with unordered associative containers is a bit more involved compared to using them for ordered associative containers. Basically, a custom hash functor needs to be implemented, containing an is_transparent type alias defined as void:
class Hasher{ public: using is_transparent = void; size_t operator()(string_view sv) const { return hash<string_view>{}(sv); }};When using this custom hasher, you also need to specify the transparent equal_to<> functor as the type for the key equality template type parameter. Here is an example:
unordered_set<string, Hasher, equal_to<>> mySet;auto i1 { mySet.find("Key") }; // No string constructed, no memory allocated.auto i2 { mySet.find("Key"sv) }; // No string constructed, no memory allocated.Logical Function Objects
Section titled “Logical Function Objects”For the three logical operations, operator!, &&, and ||, C++ provides the following function object classes: logical_not, logical_and, and logical_or. These logical operations deal only with the values true and false. Bitwise function objects are covered in the next section.
Logical functors can, for example, be used to implement an allTrue() function that checks whether all the Boolean flags in a container are true:
bool allTrue(const vector<bool>& flags){ return accumulateData(begin(flags), end(flags), true, logical_and<>{});}Similarly, the logical_or functor can be used to implement an anyTrue() function that returns true if there is at least one Boolean flag in a container true:
bool anyTrue(const vector<bool>& flags){ return accumulateData(begin(flags), end(flags), false, logical_or<>{});}Bitwise Function Objects
Section titled “Bitwise Function Objects”C++ has function objects bit_and, bit_or, bit_xor, and bit_not, corresponding to the bitwise operations operator&, |, ^, and ~. These bitwise functors can, for example, be used together with the transform() algorithm (discussed in Chapter 20) to perform bitwise operations on all elements in a container.
Adapter Function Objects
Section titled “Adapter Function Objects”When you try to use the basic function objects provided by the standard, it often feels as if you’re trying to put a square peg into a round hole. If you want to use one of the standard function objects, but the signature doesn’t quite match your requirements, then you can use adapter function objects to attempt to rectify the signature mismatch. They allow you to adapt function objects, function pointers, basically any callable. The adapters provide a modicum of support for functional composition, that is, to combine functions together to create the exact behavior you need.
Binders
Section titled “Binders”Binders can be used to bind parameters of callables to certain values. A first binder is std::bind(), defined in <functional>, which allows you to bind parameters of a callable in a flexible way. You can bind parameters to fixed values, and you can even rearrange parameters in a different order. It is best explained with an example. Suppose you have a function called func() accepting two arguments:
void func(int num, string_view str){ println("func({}, {})", num, str);}The following code demonstrates how you can use bind() to bind the second argument of func() to a fixed value, myString. The result is stored in f1(). The auto keyword is used because the return type of bind() is unspecified by the C++ standard and thus is implementation specific. Arguments that are not bound to specific values should be specified as _1, _2, _3, and so on. These are defined in the std::placeholders namespace. In the definition of f1(), the _1 specifies where the first argument to f1() needs to go when func() is called. The result: f1() can be called with just a single integer argument:
string myString { "abc" };auto f1 { bind(func, placeholders::_1, myString) };f1(16);Here is the output:
func(16, abc)bind() can also be used to rearrange the arguments, as shown in the following code. The _2 specifies where the second argument to f2() needs to go when func() is called. In other words, the f2() binding means that the first argument to f2() will become the second argument to func(), and the second argument to f2() will become the first argument to func():
auto f2 { bind(func, placeholders::_2, placeholders::_1) };f2("Test", 32);The output is as follows:
func(32, Test)As discussed in Chapter 18, <functional> defines the std::ref() and cref() helper function templates. These can be used to bind references-to-non-const and references-to-const, respectively. For example, suppose you have the following function:
void increment(int& value) { ++value; }If you call this function as follows, then the value of index becomes 1:
int index { 0 };increment(index);If you use bind() to call it as follows, then the value of index is not incremented because a copy of index is made, and a reference to this copy is bound to the first parameter of the increment() function:
auto incr { bind(increment, index) };incr();Using std::ref() to pass a proper reference correctly increments index:
auto incr { bind(increment, ref(index)) };incr();There is a small issue with binding parameters in combination with overloaded functions. Suppose you have the following two overloaded() functions. One accepts an integer, and the other accepts a floating-point number:
void overloaded(int num) {}void overloaded(float f) {}If you want to use bind() with these overloaded functions, you need to explicitly specify which of the two overloads you want to bind. The following will not compile:
auto f3 { bind(overloaded, placeholders::_1) }; // ERRORIf you want to bind the parameters of the overloaded function accepting a floating-point argument, you need the following syntax:
auto f4 { bind((void(*)(float))overloaded, placeholders::_1) }; // OKAnother example of bind() is to use findMatches() (defined earlier in this chapter) with a member function of a class as MatchHandler. For example, suppose you have the following Handler class:
class Handler{ public: void handleMatch(size_t position, int value1, int value2) { println("Match found at position {} ({}, {})", position, value1, value2); }};How to pass the handleMatch() member function as the last argument to findMatches()? The problem here is that a member function must always be called in the context of an object. Technically, every member function of a class has an implicit first parameter, containing a pointer to an object instance and accessible in the member function’s body with the name this. So, there is a signature mismatch, as our MatchHandler type accepts only three arguments: a size_t and two ints. The solution is to bind this implicit first parameter as follows:
Handler handler;findMatches(values1, values2, intEqual, bind(&Handler::handleMatch, &handler, placeholders::_1, placeholders::_2, placeholders::_3));You can also use bind() to bind parameters of standard function objects. For example, you can bind the second parameter of greater_equal to always compare with a fixed value:
auto greaterEqualTo100 { bind(greater_equal<>{}, placeholders::_1, 100) };The Standard Library provides two more binder function objects: std::bind_front() and bind_back(). The latter is introduced with C++23. They both wrap a callable f. When calling a bind_front() wrapper, the first n number of arguments for f are bound to a given set of values. For bind_back(), the last n number of arguments for f are bound. Here are two examples:
auto f5 { bind_front(func, 42) };f5("Hello");
auto f6 { bind_back(func, "Hello")};f6(42);This generates the following output:
func(42, Hello)func(42, Hello)Negator
Section titled “Negator”not_fn() is a negator, similar to a binder, but it complements the result of a callable. For example, if you want to use findMatches() to find pairs of non-equal values, you can apply the not_fn() negator adapter to the result of intEqual() like this:
findMatches(values1, values2, not_fn(intEqual), printMatch);The not_fn() functor complements the result of every call to the callable it takes as a parameter.
Calling Member Functions
Section titled “Calling Member Functions”You might want to pass a pointer to a class member function as the callback to an algorithm. For example, suppose you have the following algorithm that prints strings from a container that match a certain condition. The Matcher template type parameter is constrained with predicate<const string&> to make sure the user supplies a callback that can be called with a string parameter and returns a Boolean.
template <predicate<const string&> Matcher>void printMatchingStrings(const vector<string>& strings, Matcher matcher){ for (const auto& string : strings) { if (matcher(string)) { print("'{}' ", string); } }}You could use this algorithm to print all non-empty strings by using the empty() member function of string. However, if you just pass a pointer to string::empty() as the second argument to printMatchingStrings(), the algorithm has no way of knowing that it received a pointer to a member function instead of a normal function pointer or functor. The code to call a member function pointer is different from that of calling a normal function pointer, because the former must be called in the context of an object.
C++ provides a conversion function called mem_fn() that you can call with a member function pointer before passing it to an algorithm. The following example demonstrates this and combines it with not_fn() to invert the result of mem_fn(). Note that you have to specify the member function pointer as &string::empty. The &string:: part is not optional.
vector<string> values { "Hello", "", "", "World", "!" };printMatchingStrings(values, not_fn(mem_fn(&string::empty)));The output is as follows:
'Hello' 'World' '!'not_fn(mem_fn()) generates a function object that serves as the callback for printMatchingStrings(). Each time it is called, it calls the empty() member function on its argument and inverts the result.
POLYMORPHIC FUNCTION WRAPPERS
Section titled “POLYMORPHIC FUNCTION WRAPPERS”The C++ Standard Library provides std::function and move_only_function. Both are polymorphic function wrappers, which are function objects capable of wrapping anything that is callable such as a function, a function object, or a lambda expression; the latter is discussed later in this chapter.
std::function
Section titled “std::function”The std::function functor is defined in <functional>. An instance of std::function can be used as a function pointer, or as a parameter for a function to implement callbacks, and can be stored, copied, moved, and, of course, executed. The template parameters for the function template look a bit different than most template parameters. The syntax is as follows:
std::function<R(ArgTypes…)>R is the return type of the function, and ArgTypes is a comma-separated list of parameter types for the function.
The following example demonstrates how to use std::function to implement a function pointer. It creates a function pointer f1 to point to the function func(). Once f1 is defined, you can use it to call func():
void func(int num, string_view str) { println("func({}, {})", num, str); }
int main(){ function<void(int, string_view)> f1 { func }; f1(1, "test");}A function<R(ArgTypes…)> can store a function that has a parameter list exactly matching ArgTypes and a return type exactly of type R. It can also store any other function that has a parameter list allowing it to be called with a set of ArgTypes arguments and returning a type that can be converted to R. For example, the func() function in the previous example could accept its first parameter by const int&, while nothing in main() needs to change:
void func(const int& num, string_view str) { println("func({}, {})", num, str); }Thanks to class template argument deduction, you can simplify the creation of f1 as follows:
function f1 { func };Of course, in the preceding example, it is possible to just use the auto keyword, which removes the need to specify the type of f1. The following definition for f1 works the same and is much shorter, but the compiler-deduced type of f1 is a function pointer, that is, void (*f1)(int, string_view) instead of an std::function:
auto f1 { func };Because std::function types behave as function pointers, they can be passed to functions accepting callbacks. The original findMatches() implementation from earlier in this chapter defines two type aliases as function pointers. Those type aliases can be rewritten to use std::function, while everything else from that example remains the same:
// A type alias for a function accepting two integer values,// returning true if both values are matching, false otherwise.using Matcher = function<bool(int, int)>;
// A type alias for a function to handle a match. The first// parameter is the position of the match,// the second and third are the values that matched.using MatchHandler = function<void(size_t, int, int)>;Still, as mentioned earlier in this chapter, the recommended implementation for findMatches() uses a function template, instead of function pointers or std::function.
So, with all these examples, it looks like std::function is not really that useful; however, std::function really shines when you need to store a callback as a data member of a class. That’s the topic of one of the exercises at the end of this chapter.
std::move_only_function
Section titled “ std::move_only_function”std::function requires that the callable stored in it is copyable. To alleviate this, C++23 introduces the move-only std::move_only_function wrapper, also defined in <functional>, that can be used to wrap a move-only callable. Additionally, the move_only_function functor allows you to explicitly mark its function call operator as const and/or noexcept. This is not possible with std::function, as its function call operator is always const.
The following code snippet demonstrates move_only_function. Assume the BigData class stores a lot of data. The BigDataProcessor functor processes the data in an instance of BigData. To avoid copying, this functor stores a unique_ptr to a BigData instance, which it gets through its constructor. The function call operator is marked as const for demonstration purposes and simply prints out some text. The main() function first creates a unique_ptr of a BigData instance, creates a const processor, and finally calls the function call operator on processor. Using function instead of move_only_function in this example would not work because BigDataProcessor is a move-only type.
class BigData {};
class BigDataProcessor{ public: explicit BigDataProcessor(unique_ptr<BigData> data) : m_data { move(data) } { } void operator()() const { println("Processing BigData data…"); } private: unique_ptr<BigData> m_data;};
int main(){ auto data { make_unique<BigData>() }; const move_only_function<void() const> processor { BigDataProcessor { move(data) } }; processor();}LAMBDA EXPRESSIONS
Section titled “LAMBDA EXPRESSIONS”The clumsiness of having to create a function or functor class, give it a name that does not conflict with other names, and then use this name is considerable overhead for what is fundamentally a simple concept. In these cases, using anonymous (unnamed) functions represented by lambda expressions is a big convenience. Lambda expressions allow you to write anonymous functions inline. Their syntax is easier and can make your code more compact and easier to read. Lambda expressions are useful to define small callbacks passed to other functions inline, instead of having to define a full function object somewhere else with the callback logic implemented in its overloaded function call operator. This way, all the logic remains in a single place, and it is easier to understand and maintain. Lambda expressions can accept parameters, return values, be templated, access variables from its enclosing scope either by value or by reference, and more. There is a lot of flexibility. Let’s start with building up the syntax of lambda expressions step-by-step.
Syntax
Section titled “Syntax”Let’s start with a simple lambda expression. The following example defines a lambda expression that just writes a string to the console. A lambda expression starts with square brackets, [], called the lambda introducer, followed by curly braces, {}, which contain the body of the lambda expression. The lambda expression is assigned to the basicLambda auto-typed variable. The second line executes the lambda expression using normal function-call syntax.
auto basicLambda { []{ println("Hello from Lambda"); } };basicLambda();The output is as follows:
Hello from LambdaThe compiler automatically transforms any lambda expression to a function object, also called lambda closure, with a unique, compiler-generated name. For the previous example, the lambda expression is translated to a function object that behaves like the following function object. Note that the function call operator is a const member function and has an auto return type to let the compiler automatically deduce the return type based on the body of the member function.
class CompilerGeneratedName{ public: auto operator()() const { println("Hello from Lambda"); }};The compiler-generated name of a lambda closure can be something exotic like __Lambda_17Za. There is no way for you to figure out this name, but luckily, you don’t need to know its name.
A lambda expression can accept parameters. Parameters are specified between parentheses and multiple parameters are separated by commas, just as with normal functions. Here is an example using one parameter called value:
auto parametersLambda { [](int value){ println("The value is {}", value); } };parametersLambda(42);If a lambda expression does not accept any parameters, you can either specify empty parentheses or simply omit them.
In the compiler-generated function object for this lambda expression, the parameters are simply translated to parameters for the overloaded function call operator:
class CompilerGeneratedName{ public: auto operator()(int value) const { println("The value is {}", value); }};A lambda expression can return a value. The return type is specified following an arrow, called a trailing return type. The following example defines a lambda expression accepting two parameters and returning their sum:
auto sumLambda { [](int a, int b) -> int { return a + b; } };int sum { sumLambda(11, 22) };The return type can be omitted, in which case the compiler deduces the return type of the lambda expression according to the same rules as for function return type deduction (see Chapter 1). In the previous example, the return type can be omitted as follows:
auto sumLambda { [](int a, int b){ return a + b; } };int sum { sumLambda(11, 22) };The closure for this lambda expression behaves as follows:
class CompilerGeneratedName{ public: auto operator()(int a, int b) const { return a + b; }};The return type deduction strips any reference and const qualifiers. For example, suppose you have the following Person class:
class Person{ public: explicit Person(std::string name) : m_name { std::move(name) } { } const std::string& getName() const { return m_name; } private: std::string m_name;};The type of name1 in the following code snippet is deduced as string; hence, a copy of the person’s name is made, even though getName() returns a const string&. See Chapter 12 for a discussion of decltype(auto).
Person p { "John Doe" };decltype(auto) name1 { [] (const Person& person) { return person.getName(); }(p) };You can use a trailing return type in combination with decltype(auto) to make it so that the deduced type matches the return type of getName(), that is a const string&:
decltype(auto) name2 { [](const Person& person) -> decltype(auto) { return person.getName(); }(p) };The lambda expressions up to now in this section are called stateless because they don’t capture anything from the enclosing scope. A lambda expression can be stateful by capturing variables from its enclosing scope. For example, the following lambda expression captures the variable data so that it can be used in its body:
double data { 1.23 };auto capturingLambda { [data]{ println("Data = {}", data); } };The square brackets part serves as a capture block. Capturing a variable means that the variable becomes available inside the body of the lambda expression. Specifying an empty capture block, [], means that no variables from the enclosing scope are captured. When you just write the name of a variable in the capture block as in the preceding example, then you are capturing that variable by value.
Captured variables become data members of the lambda closure. Variables captured by value are copied into data members of the functor. These data members have the same constness as the captured variables. In the preceding capturingLambda example, the functor gets a non-const data member called data, because the captured variable, data, is non-const. The compiler-generated functor behaves as follows:
class CompilerGeneratedName{ public: CompilerGeneratedName(const double& d) : data { d } {} auto operator()() const { println("Data = {}", data); } private: double data;};In the following example, the functor gets a const data member called data, because the captured variable is const:
const double data { 1.23 };auto capturingLambda { [data]{ println("Data = {}", data); } };As mentioned earlier, a lambda closure has an overloaded function call operator that is marked as const by default. That means that even if you capture a non-const variable by value in a lambda expression, the lambda expression is not able to modify this copy. You can mark the function call operator as non-const by specifying the lambda expression as mutable, as follows:
double data { 1.23 };auto capturingLambda { [data] () mutable { data *= 2; println("Data = {}", data); } };In this example, the non-const variable data is captured by value; thus, the functor gets a non-const data member that is a copy of data. Because of the mutable keyword, the function call operator is marked as non-const, and so the body of the lambda expression can modify its copy of data.
You can prefix the name of a variable with & to capture it by reference. The following example captures the variable data by reference so that the lambda expression can directly change data in the enclosing scope:
double data { 1.23 };auto capturingLambda { [&data]{ data *= 2; } };With this lambda expression, the compiler-generated functor contains a data member called data of type reference-to-double. When you capture a variable by reference, you have to make sure that the reference is still valid at the time the lambda expression is executed.
There are two ways to capture all variables from the enclosing scope, called capture defaults:
[=]captures all variables by value.[&]captures all variables by reference.
It is also possible to selectively decide which variables to capture and how, by specifying a capture list with an optional capture default. Variables prefixed with & are captured by reference. Variables without a prefix are captured by value. If present, the capture default must be the first element in the capture list and be either & or =. Here are some capture block examples:
[&x]captures onlyxby reference and nothing else.[x]captures onlyxby value and nothing else.[=,&x,&y]captures by value by default, except variablesxandy, which are captured by reference.[&,x]captures by reference by default, except variablex, which is captured by value.- [&x,&x] is illegal because identifiers cannot be repeated.
When a lambda expression is created in the scope of an object, e.g., inside a member function of a class, then it’s possible to capture this in several ways:
[this]captures the current object. In the body of the lambda expression you can access this object, even without usingthis->. You need to make sure that the object pointed to stays alive until the last time the lambda expression has been executed.- [*this] captures a copy of the current object. This can be useful in cases where the original object will no longer be alive when the lambda expression is executed.
- [=,this] captures everything by value and explicitly captures the
thispointer. Before C++20,[=]would implicitly capture thethispointer. In C++20, this has been deprecated, and you need to explicitly capturethisif you need it.
Here are a few notes on capture blocks:
- If a by-value (
=) or by-reference (&) capture default is specified, then it is not allowed to additionally capture specific variables by value or by reference respectively. For example,[=,x]and[&,&x]are both invalid. - Data members of an object cannot be captured, unless by using a lambda capture expression discussed later in this chapter.
- When capturing
this, either by copying thethispointer,[this], or by copying the current object,[*this], the lambda expression has access to allpublic,protected, andprivatedata members and member functions of the captured object.
It is not recommended to use a capture default, even though a capture default only captures those variables that are really used in the body of the lambda expression. By using a = capture default, you might accidentally cause an expensive copy. By using an & capture default, you might accidentally modify a variable in the enclosing scope. I recommend you explicitly specify which variables you want to capture and how.
Global variables are always captured by reference, even if asked to capture by value! For example, in the following code snippet, a capture default is used to capture everything by value. Yet, the global variable, global, is captured by reference, and its value is changed after executing the lambda.
The complete syntax of a lambda expression is as follows:
[capture_block] attributes1 (parameters) specifiers noexcept_specifier attributes2 -> return_type requires1 {body}or
[capture_block] <template_params> requires1 attributes1 (parameters) specifiers noexcept_specifier attributes2 -> return_type requires2 {body}Everything is optional except the capture block and the body:
- Capture block: Called the lambda introducer and specifies how variables from the enclosing scope are captured and made available in the body of the lambda.
- Template parameters: Allows you to write parametrized lambda expressions, discussed later in this chapter.
- Parameters: A list of parameters for the lambda expression. If the lambda expression does not require any parameters, you can omit the set of parentheses or specify an empty set,
().1 The parameter list is similar to the parameter list for normal functions. - Specifiers: The following specifiers are available:
mutable: Marks the function call operator of the lambda closure as mutable; see earlier examples.constexpr: Marks the function call operator of the lambda closure asconstexpr, so it can be evaluated at compile time. Even if omitted, the function call operator will beconstexprimplicitly if it satisfies all restrictions forconstexprfunctions.consteval:Marks the function call operator of the lambda closure asconsteval, so it becomes an immediate function that must be evaluated at compile time; see Chapter 9, “Mastering Classes and Objects.”constexprandconstevalcannot be combined.static(C++23):Marks the function call operator of the lambda closure asstatic. This can be specified only for stateless lambda expression, i.e., lambda expressions with an empty capture block! Adding this specifier allows the compiler to better optimize the generated code, especially if such stateless lambda expressions are stored inside anstd::functionormove_only_functionwrapper.
noexceptspecifier: Specifiesnoexceptclauses for the function call operator of the lambda closure, similar tonoexceptclauses for normal functions.Attributes 1 (C++23): Specifies attributes for the function call operator of the lambda closure, e.g., [[nodiscard]]. Attributes are discussed in Chapter 1.- Attributes 2: Specifies attributes for the lambda closure itself.
- Return type: The type of the returned value. If omitted, the compiler deduces the return type according to the same rules as for function return type deduction; see Chapter 1.
- Requires clause 1 and 2: Specifies template type constraints for the function call operator of the lambda closure. Chapter 12, “Writing Generic Code with Templates,” explains how such constraints can be specified.
Lambda Expressions as Parameters
Section titled “Lambda Expressions as Parameters”Lambda expressions can be passed as arguments to functions in two ways. One option is to have a function parameter of type std::function that matches the signature of the lambda expression. Another option is to use a template type parameter.
For example, a lambda expression can be passed to the findMatches() function from earlier in this chapter:
vector values1 { 2, 5, 6, 9, 10, 1, 1 };vector values2 { 4, 4, 2, 9, 0, 3, 1 };findMatches(values1, values2, [](int value1, int value2) { return value1 == value2; }, printMatch);Generic Lambda Expressions
Section titled “Generic Lambda Expressions”It is possible to use auto type deduction for parameters of lambda expressions instead of explicitly specifying concrete types for them. To specify auto-type deduction for a parameter, the type is simply specified as auto, auto&, or auto*. The type deduction rules are the same as for template argument deduction.
The following example defines a generic lambda expression called areEqual. This lambda expression is used as callback for the findMatches() function from earlier in this chapter:
// Define a generic lambda expression to find equal values.auto areEqual { [](const auto& value1, const auto& value2) { return value1 == value2; } };// Use the generic lambda expression in a call to findMatches().vector values1 { 2, 5, 6, 9, 10, 1, 1 };vector values2 { 4, 4, 2, 9, 0, 3, 1 };findMatches(values1, values2, areEqual, printMatch);The compiler-generated functor for this generic lambda expression behaves like this:
class CompilerGeneratedName{ public: template <typename T1, typename T2> auto operator()(const T1& value1, const T2& value2) const { return value1 == value2; }};If the findMatches() function is modified to support not only spans of ints, but also other types, then the areEqual generic lambda expression can still be used without requiring any changes to it.
Lambda Capture Expressions
Section titled “Lambda Capture Expressions”Lambda capture expressions allow you to initialize capture variables with any expression. It can be used to introduce variables in the lambda expression that are not captured from the enclosing scope. For example, the following code creates a lambda expression with two variables in its capture block: one called myCapture, initialized with the string “Pi: ” using a lambda capture expression, and one called pi, which is captured by value from the enclosing scope. Note that non-reference capture variables such as myCapture that are initialized with a capture initializer are copy constructed, which means that const qualifiers are stripped.
double pi { 3.1415 };auto myLambda { [myCapture = "Pi: ", pi]{ println("{}{}", myCapture, pi); } };A lambda capture variable can be initialized with any expression and thus also with std::move(). This is important for objects that cannot be copied, only moved, such as unique_ptr. By default, capturing by value uses copy semantics, so it’s impossible to capture a unique_ptr by value in a lambda expression. Using a lambda capture expression, it is possible to capture it by moving, as in this example:
auto myPtr { make_unique<double>(3.1415) };auto myLambda { [p = move(myPtr)]{ println("{}", *p); } };It is allowed, though not recommended, to have the same name for the capture variable as the name in the enclosing scope. The previous example can be written as follows:
auto myPtr { make_unique<double>(3.1415) };auto myLambda { [myPtr = move(myPtr)]{ println("{}", *myPtr); } };Templated Lambda Expressions
Section titled “Templated Lambda Expressions”Templated lambda expressions allow you to get easier access to type information of parameters of generic lambda expressions. For example, suppose you have a lambda expression that requires a vector to be passed as an argument. However, the type of elements in the vector can be anything; hence, it’s a generic lambda expression using auto for its parameter. The body of the lambda expression wants to figure out what the type of the elements in the vector is. Without templated lambda expressions, this could be done using decltype() and the std::decay_t type trait. Type traits are explained in Chapter 26, “Advanced Templates,” but those details are not important to grasp the benefits of templated lambda expressions. It suffices to know that decay_t removes, among other things, any const and reference qualifications from a type. Here is the generic lambda expression:
auto lambda { [](const auto& values) { using V = decay_t<decltype(values)>; // The real type of the vector. using T = typename V::value_type; // The type of the elements of the vector. T someValue { };} };You can call this lambda expression as follows:
vector values { 1, 2, 100, 5, 6 };lambda(values);Using decltype() and decay_t is rather convoluted. A templated lambda expression makes this much easier. The following lambda expression forces its parameter to be a vector but still uses a template type parameter for the vector’s element type:
auto lambda { [] <typename T> (const vector<T> & values) { T someValue { };} };Another use of templated lambda expressions is if you want to put certain restrictions on generic lambda expressions. For example, suppose you have the following generic lambda expression:
[](const auto& value1, const auto& value2) { /* … */ }This lambda expression accepts two parameters, and the compiler deduces the type of each parameter automatically. Since the type of both parameters is deduced separately, the type of value1 and value2 could be different. If you want to restrict this and want both parameters to have the same type, you can turn this into a templated lambda expression:
[] <typename T> (const T& value1, const T& value2) { /* … */ }You can also put constraints on the template types by adding a requires clause, discussed in Chapter 12. Here’s an example:
[] <typename T> (const T& value1, const T& value2) requires integral<T> {/* … */}Lambda Expressions as Return Type
Section titled “Lambda Expressions as Return Type”By using std::function, discussed earlier in this chapter, lambda expressions can be returned from functions. Take a look at the following definition:
function<int(void)> multiplyBy2Lambda(int x){ return [x]{ return 2 * x; };}The body of this function creates a lambda expression that captures the variable x from the enclosing scope by value and returns an integer that is two times the value passed to multiplyBy2Lambda(). The return type of the multiplyBy2Lambda() function is function<int(void)>, which is a function accepting no arguments and returning an integer. The lambda expression defined in the body of the function exactly matches this prototype. The variable x is captured by value, and thus a copy of the value of x is bound to the x in the lambda expression before the lambda is returned from the function. The function can be called as follows:
function<int(void)> fn { multiplyBy2Lambda(5) };println("{}", fn());You can use the auto keyword to make this easier:
auto fn { multiplyBy2Lambda(5) };println("{}", fn());The output is 10.
Function return type deduction (see Chapter 1) allows you to write the multiplyBy2Lambda() function more elegantly, as follows:
auto multiplyBy2Lambda(int x){ return [x]{ return 2 * x; };}The multiplyBy2Lambda() function captures the variable x by value, [x]. Suppose the function is rewritten to capture the variable by reference, [&x], as follows. This will not work because the lambda expression will be executed later in the program, no longer in the scope of the multiplyBy2Lambda() function, at which point the reference to x is not valid anymore.
auto multiplyBy2Lambda(int x){ return [&x]{ return 2 * x; }; // BUG!}Lambda Expressions in Unevaluated Contexts
Section titled “Lambda Expressions in Unevaluated Contexts”Lambda expressions can be used in unevaluated contexts. For instance, the argument passed to decltype() is used only at compile time and never evaluated. Here is an example of using decltype() with a lambda expression:
using LambdaType = decltype([](int a, int b) { return a + b; });Default Construction, Copying, and Assigning
Section titled “Default Construction, Copying, and Assigning”Stateless lambda expressions can be default constructed, copied, and assigned to. Here is a quick example:
auto lambda { [](int a, int b) { return a + b; } }; // A stateless lambda.decltype(lambda) lambda2; // Default construction.auto copy { lambda }; // Copy construction.copy = lambda2; // Copy assignment.Combined with using lambda expressions in unevaluated contexts, the following kind of code is valid:
using LambdaType = decltype([](int a, int b) { return a + b; }); // Unevaluated.LambdaType getLambda() { return LambdaType{}; /* Default construction. */ }You can test this function as follows:
println("{}", getLambda()(1, 2)); Recursive Lambda Expressions
Section titled “ Recursive Lambda Expressions”For a normal lambda expression, it is not trivial for it to call itself from within its body, even when you give a name to the lambda expression. For example, the following lambda expression given the name fibonacci tries to call itself twice in the second return statement. This lambda expression will not compile.
auto fibonacci = [](int n) { if (n < 2) { return n; } return fibonacci(n - 1) + fibonacci(n - 2); // Error: does not compile!};With C++23’s explicit object parameters feature, introduced in Chapter 8, “Gaining Proficiency with Classes and Objects,” it is possible to do exactly that. This allows you to write recursive lambda expressions. The following demonstrates such a recursive lambda expression. It uses the explicit object parameter named self and calls itself recursively twice in the second return statement.
auto fibonacci = [](this auto& self, int n) { if (n < 2) { return n; } return self(n - 1) + self(n - 2);};This recursive lambda expression can be tested as follows:
println("First 20 Fibonacci numbers:");for (int i { 0 }; i < 20; ++i) { print("{} ", fibonacci(i)); }The output is as expected:
First 20 Fibonacci numbers:0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181INVOKERS
Section titled “INVOKERS”std::invoke(), defined in <functional>, can be used to call any callable with a set of arguments. The following example uses invoke() three times: once to invoke a normal function, once to invoke a lambda expression, and once to invoke a member function on a string instance:
void printMessage(string_view message) { println("{}", message); }
int main(){ invoke(printMessage, "Hello invoke."); invoke([](const auto& msg) { println("{}", msg); }, "Hello invoke."); string msg { "Hello invoke." }; println("{}", invoke(&string::size, msg));}The output of this code is as follows:
Hello invoke.Hello invoke.13std::invoke_r(), also defined in <functional>, allowing you to specify the return type. Here is an example:
int sum(int a, int b) { return a + b; }
int main(){ auto res1 { invoke(sum, 11, 22) }; // Type of res1 is int. auto res2 { invoke_r<double>(sum, 11, 22) }; // Type of res2 is double.}SUMMARY
Section titled “SUMMARY”This chapter explained the concept of callbacks, which are functions that are passed to other functions to customize their behavior. You’ve seen that callbacks can be function pointers, function objects, or lambda expressions. You also learned that lambda expressions allow you to write more readable code than composing operations using function objects and adapter function objects. Remember, writing readable code is as important, if not more important, than writing code that works. So, even if a lambda expression is a bit longer than an adapted function object, the lambda expression will be more readable and hence more maintainable.
Now that you are fluent in working with callbacks, it’s time to delve into the true power of the Standard Library with a discussion of its generic algorithms.
EXERCISES
Section titled “EXERCISES”By solving the following exercises, you can practice the material discussed in this chapter. Solutions to all exercises are available with the code download on the book’s website at www.wiley.com/go/proc++6e. However, if you are stuck on an exercise, first reread parts of this chapter to try to find an answer yourself before looking at the solution from the website.
- Exercise 19-1: Rewrite the
IsLargerThanfunction object example from this chapter using a lambda expression. You can find the code in the downloadable source code archive inCh19\03_FunctionObjects\01_IsLargerThan.cpp. - Exercise 19-2: Rewrite the example given for
bind()using lambda expressions instead. You can find the code in the downloadable source code archive inCh19\03_FunctionObjects\07_bind.cpp. - Exercise 19-3: Rewrite the example given for binding the class member function
Handler::handleMatch()using lambda expressions instead. You can find the code in the downloadable source code archive inCh19\03_FunctionObjects\10_FindMatchesWithMemberFunctionPointer.cpp. - Exercise 19-4: Chapter 18 introduces the
std::erase_if()function to remove elements from a container for which a certain predicate returnstrue. Now that you know everything about callbacks, write a small program that creates avectorof integers, and then useserase_if()to remove all odd values from thevector. The predicate you need to pass toerase_if()should accept a single value and return a Boolean. - Exercise 19-5: Implement a class called
Processor. The constructor should accept a callback accepting a single integer and returning an integer. Store this callback in a data member of the class. Next, add an overload for the function call operator accepting an integer and returning an integer. The implementation simply forwards the work to the stored callback. Test your class with different callbacks. - Exercise 19-6: Write a recursive lambda expression to calculate the power of a number. For example, 4 to the power 3, written as 4^3, equals 4×4×4. Make sure it works with negative exponents. To help you, 4^-3 equals 1/(4^3). Any number to the power 0 equals 1. Test your lambda expression by generating all powers of two with exponents between -10 and 10.
Footnotes
Section titled “Footnotes”-
Prior to C++23, you could only omit the empty set of parentheses if you did not need any parameters and you did not specify mutable, constexpr, consteval, a noexcept specifier, attributes, a return type, or a requires clause. ↩