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 f
’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.
Exercise 10.22: Rewrite the program to count words of size 6 or less using functions in place of the lambdas.
Exercise 10.23: How many arguments does bind
take?
Exercise 10.24: Use bind
and check_size
to find the first element in a vector
of int
s that has a value greater than the length of a specified string
value.
Exercise 10.25: In the exercises for § 10.3.2 (p. 392) you wrote a version of biggies
that uses partition
. Rewrite that function to use check_size
and bind
.