10.3.4. Binding Arguments

Image

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.

The Library bind Function
Image

We 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.

Binding the 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 strings 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.

Using placeholders Names

The _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.

Arguments to 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)

Using to bind to Reorder Parameters

As 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).

Binding Reference Parameters

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.


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.23: How many arguments does bind take?

Exercise 10.24: Use bind and check_size to find the first element in a vector of ints 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.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.14.245