Many of the algorithms compare elements in the input sequence. By default, such algorithms use either the element type’s <
or ==
operator. The library also defines versions of these algorithms that let us supply our own operation to use in place of the default operator.
For example, the sort
algorithm uses the element type’s <
operator. However, we might want to sort a sequence into a different order from that defined by <
, or our sequence might have elements of a type (such as Sales_data
) that does not have a <
operator. In both cases, we need to override the default behavior of sort
.
As one example, assume that we want to print the vector
after we call elimDups
(§ 10.2.3, p. 384). However, we’ll also assume that we want to see the words ordered by their size, and then alphabetically within each size. To reorder the vector
by length, we’ll use a second, overloaded version of sort
. This version of sort
takes a third argument that is a predicate.
A predicate is an expression that can be called and that returns a value that can be used as a condition. The predicates used by library algorithms are either unary predicates (meaning they have a single parameter) or binary predicates (meaning they have two parameters). The algorithms that take predicates call the given predicate on the elements in the input range. As a result, it must be possible to convert the element type to the parameter type of the predicate.
The version of sort
that takes a binary predicate uses the given predicate in place of <
to compare elements. The predicates that we supply to sort
must meet the requirements that we’ll describe in § 11.2.2 (p. 425). For now, what we need to know is that the operation must define a consistent order for all possible elements in the input sequence. Our isShorter
function from § 6.2.2 (p. 211) is an example of a function that meets these requirements, so we can pass isShorter
to sort
. Doing so will reorder the elements by size:
// comparison function to be used to sort by word length
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
// sort on word length, shortest to longest
sort(words.begin(), words.end(), isShorter);
If words
contains the same data as in § 10.2.3 (p. 384), this call would reorder words
so that all the words of length 3 appear before words of length 4, which in turn are followed by words of length 5, and so on.
When we sort words
by size, we also want to maintain alphabetic order among the elements that have the same length. To keep the words of the same length in alphabetical order we can use the stable_sort
algorithm. A stable sort maintains the original order among equal elements.
Ordinarily, we don’t care about the relative order of equal elements in a sorted sequence. After all, they’re equal. However, in this case, we have defined “equal” to mean “have the same length.” Elements that have the same length still differ from one another when we view their contents. By calling stable_sort
, we can maintain alphabetical order among those elements that have the same length:
elimDups(words); // put words in alphabetical order and remove duplicates
// resort by length, maintaining alphabetical order among words of the same length
stable_sort(words.begin(), words.end(), isShorter);
for (const auto &s : words) // no need to copy the strings
cout << s << " "; // print each element separated by a space
cout << endl;
Assuming words
was in alphabetical order before this call, after the call, words
will be sorted by element size, and the words of each length remain in alphabetical order. If we run this code on our original vector
, the output will be
fox red the over slow jumps quick turtle
Exercises Section 10.3.1
Exercise 10.11: Write a program that uses
stable_sort
andisShorter
to sort avector
passed to your version ofelimDups
. Print thevector
to verify that your program is correct.Exercise 10.12: Write a function named
compareIsbn
that compares theisbn()
members of twoSales_data
objects. Use that function tosort
avector
that holdsSales_data
objects.Exercise 10.13: The library defines an algorithm named
partition
that takes a predicate and partitions the container so that values for which the predicate istrue
appear in the first part and those for which the predicate isfalse
appear in the second part. The algorithm returns an iterator just past the last element for which the predicate returnedtrue
. Write a function that takes astring
and returns abool
indicating whether thestring
has five characters or more. Use that function to partitionwords
. Print the elements that have five or more characters.
The predicates we pass to an algorithm must have exactly one or two parameters, depending on whether the algorithm takes a unary or binary predicate, respectively. However, sometimes we want to do processing that requires more arguments than the algorithm’s predicate allows. For example, the solution you wrote for the last exercise in the previous section had to hard-wire the size 5 into the predicate used to partition the sequence. It would be move useful to be able to partition a sequence without having to write a separate predicate for every possible size.
As a related example, we’ll revise our program from § 10.3.1 (p. 387) to report how many words are of a given size or greater. We’ll also change the output so that it prints only the words of the given length or greater.
A sketch of this function, which we’ll name biggies
, is as follows:
void biggies(vector<string> &words,
vector<string>::size_type sz)
{
elimDups(words); // put words in alphabetical order and remove duplicates
// resort by length, maintaining alphabetical order among words of the same length
stable_sort(words.begin(), words.end(), isShorter);
// get an iterator to the first element whose size() is >= sz
// compute the number of elements with size >= sz
// print words of the given size or longer, each one followed by a space
}
Our new problem is to find the first element in the vector
that has the given size. Once we know that element, we can use its position to compute how many elements have that size or greater.
We can use the library find_if
algorithm to find an element that has a particular size. Like find
(§ 10.1, p. 376), the find_if
algorithm takes a pair of iterators denoting a range. Unlike find
, the third argument to find_if
is a predicate. The find_if
algorithm calls the given predicate on each element in the input range. It returns the first element for which the predicate returns a nonzero value, or its end iterator if no such element is found.
It would be easy to write a function that takes a string
and a size and returns a bool
indicating whether the size of a given string
is greater than the given size. However, find_if
takes a unary predicate—any function we pass to find_if
must have exactly one parameter that can be called with an element from the input sequence. There is no way to pass a second argument representing the size. To solve this part of our problem we’ll need to use some additional language facilities.
We can pass any kind of callable object to an algorithm. An object or expression is callable if we can apply the call operator (§ 1.5.2, p. 23) to it. That is, if e
is a callable expression, we can write e(args)
where args is a comma-separated list of zero or more arguments.
The only callables we’ve used so far are functions and function pointers (§ 6.7, p. 247). There are two other kinds of callables: classes that overload the function-call operator, which we’ll cover in § 14.8 (p. 571), and lambda expressions.
A lambda expression represents a callable unit of code. It can be thought of as an unnamed, inline function. Like any function, a lambda has a return type, a parameter list, and a function body. Unlike a function, lambdas may be defined inside a function. A lamba expression has the form
[capture list] (parameter list) -> return type { function body }
where capture list is an (often empty) list of local variables defined in the enclosing function; return type, parameter list, and function body are the same as in any ordinary function. However, unlike ordinary functions, a lambda must use a trailing return (§ 6.3.3, p. 229) to specify its return type.
We can omit either or both of the parameter list and return type but must always include the capture list and function body:
Here, we’ve defined f
as a callable object that takes no arguments and returns 42
.
We call a lambda the same way we call a function by using the call operator:
cout << f() << endl; // prints 42
Omitting the parentheses and the parameter list in a lambda is equivalent to specifying an empty parameter list. Hence, when we call f
, the argument list is empty. If we omit the return type, the lambda has an inferred return type that depends on the code in the function body. If the function body is just a return
statement, the return type is inferred from the type of the expression that is returned. Otherwise, the return type is void
.
Lambdas with function bodies that contain anything other than a single
return
statement that do not specify a return type returnvoid
.
As with an ordinary function call, the arguments in a call to a lambda are used to initialize the lambda’s parameters. As usual, the argument and parameter types must match. Unlike ordinary functions, a lambda may not have default arguments (§ 6.5.1, p. 236). Therefore, a call to a lambda always has as many arguments as the lambda has parameters. Once the parameters are initialized, the function body executes.
As an example of a lambda that takes arguments, we can write a lambda that behaves like our isShorter
function:
[](const string &a, const string &b)
{ return a.size() < b.size();}
The empty capture list indicates that this lambda will not use any local variables from the surrounding function. The lambda’s parameters, like the parameters to isShorter
, are references to const string
. Again like isShorter
, the lambda’s function body compares its parameters’ size()
s and returns a bool
that depends on the relative sizes of the given arguments.
We can rewrite our call to stable_sort
to use this lambda as follows:
// sort words by size, but maintain alphabetical order for words of the same size
stable_sort(words.begin(), words.end(),
[](const string &a, const string &b)
{ return a.size() < b.size();});
When stable_sort
needs to compare two elements, it will call the given lambda expression.
We’re now ready to solve our original problem, which is to write a callable expression that we can pass to find_if
. We want an expression that will compare the length of each string
in the input sequence with the value of the sz
parameter in the biggies
function.
Although a lambda may appear inside a function, it can use variables local to that function only if it specifies which variables it intends to use. A lambda specifies the variables it will use by including those local variables in its capture list. The capture list directs the lambda to include information needed to access those variables within the lambda itself.
In this case, our lambda will capture sz
and will have a single string
parameter. The body of our lambda will compare the given string
’s size with the captured value of sz
:
[sz](const string &a)
{ return a.size() >= sz; };
Inside the []
that begins a lambda we can provide a comma-separated list of names defined in the surrounding function.
Because this lambda captures sz
, the body of the lambda may use sz
. The lambda does not capture words
, and so has no access to that variable. Had we given our lambda an empty capture list, our code would not compile:
// error: sz not captured
[](const string &a)
{ return a.size() >= sz; };
A lambda may use a variable local to its surrounding function only if the lambda captures that variable in its capture list.
find_if
Using this lambda, we can find the first element whose size is at least as big as sz
:
// get an iterator to the first element whose size() is >= sz
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a)
{ return a.size() >= sz; });
The call to find_if
returns an iterator to the first element that is at least as long as the given sz
, or a copy of words.end()
if no such element exists.
We can use the iterator returned from find_if
to compute how many elements appear between that iterator and the end of words
(§ 3.4.2, p. 111):
// compute the number of elements with size >= sz
auto count = words.end() - wc;
cout << count << " " << make_plural(count, "word", "s")
<< " of length " << sz << " or longer" << endl;
Our output statement calls make_plural
(§ 6.3.2, p. 224) to print word
or words
, depending on whether that size is equal to 1.
for_each
AlgorithmThe last part of our problem is to print the elements in words
that have length sz
or greater. To do so, we’ll use the for_each
algorithm. This algorithm takes a callable object and calls that object on each element in the input range:
// print words of the given size or longer, each one followed by a space
for_each(wc, words.end(),
[](const string &s){cout << s << " ";});
cout << endl;
The capture list in this lambda is empty, yet the body uses two names: its own parameter, named s
, and cout
.
The capture list is empty, because we use the capture list only for (nonstatic
) variables defined in the surrounding function. A lambda can use names that are defined outside the function in which the lambda appears. In this case, cout
is not a name defined locally in biggies;
that name is defined in the iostream
header. So long as the iostream
header is included in the scope in which biggies
appears, our lambda can use cout
.
The capture list is used for local non
static
variables only; lambdas can use localstatic
s and variables declared outside the function directly.
Now that we’ve looked at the program in detail, here is the program as a whole:
void biggies(vector<string> &words,
vector<string>::size_type sz)
{
elimDups(words); // put words in alphabetical order and remove duplicates
// sort words by size, but maintain alphabetical order for words of the same size
stable_sort(words.begin(), words.end(),
[](const string &a, const string &b)
{ return a.size() < b.size();});
// get an iterator to the first element whose size() is >= sz
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a)
{ return a.size() >= sz; });
// compute the number of elements with size >= sz
auto count = words.end() - wc;
cout << count << " " << make_plural(count, "word", "s")
<< " of length " << sz << " or longer" << endl;
// print words of the given size or longer, each one followed by a space
for_each(wc, words.end(),
[](const string &s){cout << s << " ";});
cout << endl;
}
Exercises Section 10.3.2
Exercise 10.14: Write a lambda that takes two
int
s and returns their sum.Exercise 10.15: Write a lambda that captures an
int
from its enclosing function and takes anint
parameter. The lambda should return the sum of the capturedint
and theint
parameter.Exercise 10.16: Write your own version of the
biggies
function using lambdas.Exercise 10.17: Rewrite exercise 10.12 from § 10.3.1 (p. 387) to use a lambda in the call to
sort
instead of thecompareIsbn
function.Exercise 10.18: Rewrite
biggies
to usepartition
instead offind_if
. We described thepartition
algorithm in exercise 10.13 in § 10.3.1 (p. 387).Exercise 10.19: Rewrite the previous exercise to use
stable_partition
, which likestable_sort
maintains the original element order in the paritioned sequence.
When we define a lambda, the compiler generates a new (unnamed) class type that corresponds to that lambda. We’ll see how these classes are generated in § 14.8.1 (p. 572). For now, what’s useful to understand is that when we pass a lambda to a function, we are defining both a new type and an object of that type: The argument is an unnamed object of this compiler-generated class type. Similarly, when we use auto
to define a variable initialized by a lambda, we are defining an object of the type generated from that lambda.
By default, the class generated from a lambda contains a data member corresponding to the variables captured by the lambda. Like the data members of any class, the data members of a lambda are initialized when a lambda object is created.
Similar to parameter passing, we can capture variables by value or by reference. Table 10.1 (p. 395) covers the various ways we can form a capture list. So far, our lambdas have captured variables by value. As with a parameter passed by value, it must be possible to copy such variables. Unlike parameters, the value of a captured variable is copied when the lambda is created, not when it is called:
void fcn1()
{
size_t v1 = 42; // local variable
// copies v1 into the callable object named f
auto f = [v1] { return v1; };
v1 = 0;
auto j = f(); // j is 42; f stored a copy of v1 when we created it
}
Table 10.1. Lambda Capture List
Because the value is copied when the lambda is created, subsequent changes to a captured variable have no effect on the corresponding value inside the lambda.
We can also define lambdas that capture variables by reference. For example:
void fcn2()
{
size_t v1 = 42; // local variable
// the object f2 contains a reference to v1
auto f2 = [&v1] { return v1; };
v1 = 0;
auto j = f2(); // j is 0; f2 refers to v1; it doesn't store it
}
The &
before v1
indicates that v1
should be captured as a reference. A variable captured by reference acts like any other reference. When we use the variable inside the lambda body, we are using the object to which that reference is bound. In this case, when the lambda returns v1
, it returns the value of the object to which v1
refers.
Reference captures have the same problems and restrictions as reference returns (§ 6.3.2, p. 225). If we capture a variable by reference, we must be certain that the referenced object exists at the time the lambda is executed. The variables captured by a lambda are local variables. These variables cease to exist once the function completes. If it is possible for a lambda to be executed after the function finishes, the local variables to which the capture refers no longer exist.
Reference captures are sometimes necessary. For example, we might want our biggies
function to take a reference to an ostream
on which to write and a character to use as the separator:
void biggies(vector<string> &words,
vector<string>::size_type sz,
ostream &os = cout, char c = ' ')
{
// code to reorder words as before
// statement to print count revised to print to os
for_each(words.begin(), words.end(),
[&os, c](const string &s) { os << s << c; });
}
We cannot copy ostream
objects (§ 8.1.1, p. 311); the only way to capture os
is by reference (or through a pointer to os
).
When we pass a lambda to a function, as in this call to for_each
, the lambda executes immediately. Capturing os
by reference is fine, because the variables in biggies
exist while for_each
is running.
We can also return a lambda from a function. The function might directly return a callable object or the function might return an object of a class that has a callable object as a data member. If the function returns a lambda, then—for the same reasons that a function must not return a reference to a local variable—that lambda must not contain reference captures.
When we capture a variable by reference, we must ensure that the variable exists at the time that the lambda executes.
A lambda capture stores information between the time the lambda is created (i.e., when the code that defines the lambda is executed) and the time (or times) the lambda itself is executed. It is the programmer’s responsibility to ensure that whatever information is captured has the intended meaning each time the lambda is executed.
Capturing an ordinary variable—an
int
, astring
, or other nonpointer type—by value is usually straightforward. In this case, we only need to care whether the variable has the value we need when we capture it.If we capture a pointer or iterator, or capture a variable by reference, we must ensure that the object bound to that iterator, pointer, or reference still exists, whenever the lambda executes. Moreover, we need to ensure that the object has the intended value. Code that executes between when the lambda is created and when it executes might change the value of the object to which the lambda capture points (or refers). The value of the object at the time the pointer (or reference) was captured might have been what we wanted. The value of that object when the lambda executes might be quite different.
As a rule, we can avoid potential problems with captures by minimizing the data we capture. Moreover, if possible, avoid capturing pointers or references.
Rather than explicitly listing the variables we want to use from the enclosing function, we can let the compiler infer which variables we use from the code in the lambda’s body. To direct the compiler to infer the capture list, we use an &
or =
in the capture list. The &
tells the compiler to capture by reference, and the =
says the values are captured by value. For example, we can rewrite the lambda that we passed to find_if
as
// sz implicitly captured by value
wc = find_if(words.begin(), words.end(),
[=](const string &s)
{ return s.size() >= sz; });
If we want to capture some variables by value and others by reference, we can mix implicit and explicit captures:
void biggies(vector<string> &words,
vector<string>::size_type sz,
ostream &os = cout, char c = ' ')
{
// other processing as before
// os implicitly captured by reference; c explicitly captured by value
for_each(words.begin(), words.end(),
[&, c](const string &s) { os << s << c; });
// os explicitly captured by reference; c implicitly captured by value
for_each(words.begin(), words.end(),
[=, &os](const string &s) { os << s << c; });
}
When we mix implicit and explicit captures, the first item in the capture list must be an &
or =
. That symbol sets the default capture mode as by reference or by value, respectively.
When we mix implicit and explicit captures, the explicitly captured variables must use the alternate form. That is, if the implicit capture is by reference (using &
), then the explicitly named variables must be captured by value; hence their names may not be preceded by an &
. Alternatively, if the implicit capture is by value (using =
), then the explicitly named variables must be preceded by an &
to indicate that they are to be captured by reference.
By default, a lambda may not change the value of a variable that it copies by value. If we want to be able to change the value of a captured variable, we must follow the parameter list with the keyword mutable
. Lambdas that are mutable may not omit the parameter list:
void fcn3()
{
size_t v1 = 42; // local variable
// f can change the value of the variables it captures
auto f = [v1] () mutable { return ++v1; };
v1 = 0;
auto j = f(); // j is 43
}
Whether a variable captured by reference can be changed (as usual) depends only on whether that reference refers to a const
or nonconst
type:
void fcn4()
{
size_t v1 = 42; // local variable
// v1 is a reference to a non const variable
// we can change that variable through the reference inside f2
auto f2 = [&v1] { return ++v1; };
v1 = 0;
auto j = f2(); // j is 1
}
The lambdas we’ve written so far contain only a single return
statement. As a result, we haven’t had to specify the return type. By default, if a lambda body contains any statements other than a return
, that lambda is assumed to return void
. Like other functions that return void
, lambdas inferred to return void
may not return a value.
As a simple example, we might use the library transform
algorithm and a lambda to replace each negative value in a sequence with its absolute value:
transform(vi.begin(), vi.end(), vi.begin(),
[](int i) { return i < 0 ? -i : i; });
The transform
function takes three iterators and a callable. The first two iterators denote an input sequence and the third is a destination. The algorithm calls the given callable on each element in the input sequence and writes the result to the destination. As in this call, the destination iterator can be the same as the iterator denoting the start of the input. When the input iterator and the destination iterator are the same, transform
replaces each element in the input range with the result of calling the callable on that element.
In this call, we passed a lambda that returns the absolute value of its parameter. The lambda body is a single return
statement that returns the result of a conditional expression. We need not specify the return type, because that type can be inferred from the type of the conditional operator.
However, if we write the seemingly equivalent program using an if
statement, our code won’t compile:
// error: cannot deduce the return type for the lambda
transform(vi.begin(), vi.end(), vi.begin(),
[](int i) { if (i < 0) return -i; else return i; });
This version of our lambda infers the return type as void
but we returned a value.
When we need to define a return type for a lambda, we must use a trailing return type (§ 6.3.3, p. 229):
transform(vi.begin(), vi.end(), vi.begin(),
[](int i) -> int
{ if (i < 0) return -i; else return i; });
In this case, the fourth argument to transform
is a lambda with an empty capture list, which takes a single parameter of type int
and returns a value of type int
. Its function body is an if
statement that returns the absolute value of its parameter.
Exercises Section 10.3.3
Exercise 10.20: The library defines an algorithm named
count_if
. Likefind_if
, this function takes a pair of iterators denoting an input range and a predicate that it applies to each element in the given range.count_if
returns a count of how often the predicate is true. Usecount_if
to rewrite the portion of our program that counted how many words are greater than length 6.Exercise 10.21: Write a lambda that captures a local
int
variable and decrements that variable until it reaches 0. Once the variable is 0 additional calls should no longer decrement the variable. The lambda should return abool
that indicates whether the captured variable is 0.
Lambda expressions are most useful for simple operations that we do not need to use in more than one or two places. If we need to do the same operation in many places, we should usually define a function rather than writing the same lambda expression multiple times. Similarly, if an operation requires many statements, it is ordinarily better to use a function.
It is usually straightforward to use a function in place of a lambda that has an empty capture list. As we’ve seen, we can use either a lambda or our isShorter
function to order the vector
on word length. Similarly, it would be easy to replace the lambda that printed the contents of our vector
by writing a function that takes a string
and prints the given string
to the standard output.
However, it is not so easy to write a function to replace a lambda that captures local variables. For example, the lambda that we used in the call to find_if
compared a string
with a given size. We can easily write a function to do the same work:
bool check_size(const string &s, string::size_type sz)
{
return s.size() >= sz;
}
However, we can’t use this function as an argument to find_if
. As we’ve seen, find_if
takes a unary predicate, so the callable passed to find_if
must take a single argument. The lambda that biggies
passed to find_if
used its capture list to store sz
. In order to use check_size
in place of that lambda, we have to figure out how to pass an argument to the sz
parameter.
bind
FunctionWe can solve the problem of passing a size argument to check_size
by using a new library function named bind
, which is defined in the functional
header. The bind
function can be thought of as a general-purpose function adaptor (§ 9.6, p. 368). It takes a callable object and generates a new callable that “adapts” the parameter list of the original object.
The general form of a call to bind
is:
auto newCallable = bind(callable, arg_list);
where newCallable is itself a callable object and arg_list is a comma-separated list of arguments that correspond to the parameters of the given callable. That is, when we call newCallable, newCallable calls callable, passing the arguments in arg_list.
The arguments in arg_list may include names of the form _
n, where n is an integer. These arguments are “placeholders” representing the parameters of newCallable. They stand “in place of” the arguments that will be passed to newCallable. The number n is the position of the parameter in the generated callable: _1
is the first parameter in newCallable, _2
is the second, and so forth.
sz
Parameter of check_size
As a simple example, we’ll use bind
to generate an object that calls check_size
with a fixed value for its size parameter as follows:
// check6 is a callable object that takes one argument of type string
// and calls check_size on its given string and the value 6
auto check6 = bind(check_size, _1, 6);
This call to bind
has only one placeholder, which means that check6
takes a single argument. The placeholder appears first in arg_list, which means that the parameter in check6
corresponds to the first parameter of check_size
. That parameter is a const string&
, which means that the parameter in check6
is also a const string&
. Thus, a call to check6
must pass an argument of type string
, which check6
will pass as the first argument to check_size
.
The second argument in arg_list (i.e., the third argument to bind
) is the value 6
. That value is bound to the second parameter of check_size
. Whenever we call check6
, it will pass 6
as the second argument to check_size
:
string s = "hello";
bool b1 = check6(s); // check6(s) calls check_size(s, 6)
Using bind
, we can replace our original lambda-based call to find_if
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a)
with a version that uses check_size
:
auto wc = find_if(words.begin(), words.end(),
bind(check_size, _1, sz));
This call to bind
generates a callable object that binds the second argument of check_size
to the value of sz
. When find_if
calls this object on the string
s in words
those calls will in turn call check_size
passing the given string
and sz
. So, find_if
(effectively) will call check_size
on each string
in the input range and compare the size of that string
to sz
.
placeholders
NamesThe _
n names are defined in a namespace named placeholders
. That namespace is itself defined inside the std
namespace (§ 3.1, p. 82). To use these names, we must supply the names of both namespaces. As with our other examples, our calls to bind
assume the existence of appropriate using
declarations. For example, the using
declaration for _1
is
using std::placeholders::_1;
This declaration says we’re using the name _1
, which is defined in the namespace placeholders
, which is itself defined in the namespace std
.
We must provide a separate using
declaration for each placeholder name that we use. Writing such declarations can be tedious and error-prone. Rather than separately declaring each placeholder, we can use a different form of using
that we will cover in more detail in § 18.2.2 (p. 793). This form:
using namespace namespace_name;
says that we want to make all the names from namespace_name accessible to our program. For example:
using namespace std::placeholders;
makes all the names defined by placeholders
usable. Like the bind
function, the placeholders
namespace is defined in the functional
header.
bind
As we’ve seen, we can use bind
to fix the value of a parameter. More generally, we can use bind
to bind or rearrange the parameters in the given callable. For example, assuming f
is a callable object that has five parameters, the following call to bind
:
// g is a callable object that takes two arguments
auto g = bind(f, a, b, _2, c, _1);
generates a new callable that takes two arguments, represented by the placeholders _2
and _1
. The new callable will pass its own arguments as the third and fifth arguments to f
. The first, second, and fourth arguments to f
are bound to the given values, a
, b
, and c
, respectively.
The arguments to g
are bound positionally to the placeholders. That is, the first argument to g
is bound to _1
, and the second argument is bound to _2
. Thus, when we call g
, the first argument to g
will be passed as the last argument to f
; the second argument to g
will be passed as g
’s third argument. In effect, this call to bind
maps
g(_1, _2)
to
f(a, b, _2, c, _1)
That is, calling g
calls f
using g
’s arguments for the placeholders along with the bound arguments, a
, b
, and c
. For example, calling g(X, Y)
calls
f(a, b, Y, c, X)
bind
to Reorder ParametersAs a more concrete example of using bind
to reorder arguments, we can use bind
to invert the meaning of isShorter
by writing
// sort on word length, shortest to longest
sort(words.begin(), words.end(), isShorter);
// sort on word length, longest to shortest
sort(words.begin(), words.end(), bind(isShorter, _2, _1));
In the first call, when sort
needs to compare two elements, A
and B
, it will call isShorter(A, B)
. In the second call to sort
, the arguments to isShorter
are swapped. In this case, when sort
compares elements, it will be as if sort
called isShorter(B, A)
.
By default, the arguments to bind
that are not placeholders are copied into the callable object that bind
returns. However, as with lambdas, sometimes we have arguments that we want to bind but that we want to pass by reference or we might want to bind an argument that has a type that we cannot copy.
For example, to replace the lambda that captured an ostream
by reference:
// os is a local variable referring to an output stream
// c is a local variable of type char
for_each(words.begin(), words.end(),
[&os, c](const string &s) { os << s << c; });
We can easily write a function to do the same job:
ostream &print(ostream &os, const string &s, char c)
{
return os << s << c;
}
However, we can’t use bind
directly to replace the capture of os
:
// error: cannot copy os
for_each(words.begin(), words.end(), bind(print, os, _1, ' '));
because bind
copies its arguments and we cannot copy an ostream
. If we want to pass an object to bind
without copying it, we must use the library ref
function:
for_each(words.begin(), words.end(),
bind(print, ref(os), _1, ' '));
The ref
function returns an object that contains the given reference and that is itself copyable. There is also a cref
function that generates a class that holds a reference to const
. Like bind
, the ref
and cref
functions are defined in the functional
header.
Older versions of C++ provided a much more limited, yet more complicated, set of facilities to bind arguments to functions. The library defined two functions named
bind1st
andbind2nd
. Likebind
, these functions take a function and generate a new callable object that calls the given function with one of its parameters bound to a given value. However, these functions can bind only the first or second parameter, respectively. Because they are of much more limited utility, they have been deprecated in the new standard. A deprecated feature is one that may not be supported in future releases. Modern C++ programs should usebind
.
Exercises Section 10.3.4
Exercise 10.22: Rewrite the program to count words of size 6 or less using functions in place of the lambdas.
Exercise 10.24: Use
bind
andcheck_size
to find the first element in avector
ofint
s that has a value greater than the length of a specifiedstring
value.Exercise 10.25: In the exercises for § 10.3.2 (p. 392) you wrote a version of
biggies
that usespartition
. Rewrite that function to usecheck_size
andbind
.