Team LiB
Previous Section Next Section

10.3. Customizing Operations

 

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.

 

10.3.1. Passing a Function to an Algorithm

 
Image

As one example, assume that we want to print the vector after we call elimDups10.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.

 
Predicates
 

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.

 
Sorting Algorithms
 

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 and isShorter to sort a vector passed to your version of elimDups. Print the vector to verify that your program is correct.

Exercise 10.12: Write a function named compareIsbn that compares the isbn() members of two Sales_data objects. Use that function to sort a vector that holds Sales_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 is true appear in the first part and those for which the predicate is false appear in the second part. The algorithm returns an iterator just past the last element for which the predicate returned true. Write a function that takes a string and returns a bool indicating whether the string has five characters or more. Use that function to partition words. Print the elements that have five or more characters.


 

10.3.2. Lambda Expressions

 

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

 
Introducing Lambdas
 

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.

 
Image

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:

 

auto f = [] { return 42; };

 

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.

 

Image Note

Lambdas with function bodies that contain anything other than a single return statement that do not specify a return type return void.

 

 
Passing Arguments to a Lambda
 

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.

 
Using the Capture List
 

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; };

 

Image Note

A lambda may use a variable local to its surrounding function only if the lambda captures that variable in its capture list.

 

 
Calling 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 words3.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_plural6.3.2, p. 224) to print word or words, depending on whether that size is equal to 1.

 
The for_each Algorithm
 

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

 

Image Note

The capture list is used for local nonstatic variables only; lambdas can use local statics and variables declared outside the function directly.

 

 
Putting It All Together
 

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 ints and returns their sum.

Exercise 10.15: Write a lambda that captures an int from its enclosing function and takes an int parameter. The lambda should return the sum of the captured int and the int 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 the compareIsbn function.

 

Exercise 10.18: Rewrite biggies to use partition instead of find_if. We described the partition algorithm in exercise 10.13 in § 10.3.1 (p. 387).

 

Exercise 10.19: Rewrite the previous exercise to use stable_partition, which like stable_sort maintains the original element order in the paritioned sequence.


 

10.3.3. Lambda Captures and Returns

 

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.

 
Capture by Value
 

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

 
Image
 

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.

 
Capture by Reference
 

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.

 

Image Warning

When we capture a variable by reference, we must ensure that the variable exists at the time that the lambda executes.

 

 

Advice: Keep Your Lambda Captures Simple

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, a string, 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.

 

 
Implicit Captures
 

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.

 
Mutable Lambdas
 

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
}

 
Specifying the Lambda Return Type
 

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.

 
Image

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. Like find_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. Use count_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 a bool that indicates whether the captured variable is 0.


 

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

 
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.

 

Backward Compatibility: Binding Arguments

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 and bind2nd. Like bind, 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 use bind.

 

 

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.

 

 
Team LiB
Previous Section Next Section