Chapter 10. Generic Algorithms
10.1 Overview
Most of the algorithms are defined in the algorithm
header. The library also defines a set of generic numeric algorithms that are defined in the numeric
header.
The iterator dereference operator gives access to an element's value.
Although iterators make the algorithms container independent, most of the algorithms use one operation on the element type.
10.2 A First Look at the Algorithms
10.2.1 Read-Only Algorithms
A number of the algorithms read, but never write to , the elements in their input range. The accumulate
function takes three arguments. The first two specify a range of elements to sum. The third is an initial value fro the sum.
Another read-only algorithm is equal, which lets us determine whether two sequences hold the same values. The algorithm takes three iterators: The first two denote the range of elements in the first sequence; the third denotes the first element in the second sequence:
10.2.2 Algorithms That Write Container Elements
Some algorithms assign new values to the elements in a sequence. The fill
algorithm takes a pair of iterators that denote a range and a third argument that is a value. fill
assigns the given value to each element in the input sequence:
The fill_n
function takes a single iterator, a count, and a value. It assigns the given value to the specified number of elements starting at the element denoted by the iterator.
Algorithms that write to a destination iterator assume the destination is large enough to hold the number of elements being written.
An insert iterator is an iterator that adds elements to a container. A back inserter is a function defined in the iterator header. back_inserter
takes a reference to a container and returns an insert iterator bound to that container.
Since passed an iterator returned by back_inserter
, each assignment will call push_back
on vec
.
This algorithm takes three iterators. The first two denote an input range; the third denotes the beginning of the destination sequence.
The replace
algorithm reads a sequence and replaces every instance of a given value with another value. This algorithm takes four parameters: two iterators denoting the input range, and two values. It replaces each element that is equal to the first value with the second:
If we want to leave the original sequence unchanged, we can call replace_copy. That algorithm takes a third iterator argument denoting a destination in which to write the adjusted sequence:
10.2.3 Algorithms That Reorder Container Elements
A call of sort
arranges the elements in the input range into sorted order using the element type's <
operator.
The unique
algorithm rearranges the input range to "eliminate" adjacent duplicated entries, and returns an iterator that denotes the end of the range of the unique values. The size of words is unchanged.
10.3 Customising Operation
10.3.1 Passing a Function to an Algorithm
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 or binary predicates.
The version of sort
that takes a binary predicate uses the given predicate in place of <
to compare elements.
A stable_sort
algorithm maintains the original order among equal elements.
10.3.2 Lambda Expressions
A lamba
expression has the form:
where capture list
is a list of local variables defined in the enclosing function; return type
, parameter list
, and function body
are the same as in any ordinary function.
Lambdas with function bodies that contain anything other than a single return statement that do not specify a return type return void.
The empty capture list indicates that this lambda will not use any local variables from the surrounding function.
A lambda may use a variable local to its surrounding function only if the lambda captures that variable in tis capture list. The capture list is used for local nonstatic
variables only; lambdas can use local statics
and variable declared outside the function directly.
10.3.3 Lambda Captures and Returns
Be 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
.
If we write the seemingly equivalent program using an if statement, our code won't compile:
When we need to define a return type for a lambda, we must use a trailing return type:
10.3.4 Binding Arguments
The bind, which is defined in the functional
heard. The bind function can be thought of as a general-purpose function adaptor. 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:
We can use bind to fix the value of a parameter, assuming f is callable object that has five parameters, the following call to bind:
In effect, this call to bind maps:
We can also use bind to invert the meaning:
Binding reference parameters
There is also a cref
function that generates class that holds a reference to const.
10.4 Revisiting Iterators
The library defines several additional kinds of iterators in the iterator
header. These iterators include:
Insert iterators: These iterators are bound to a container and can be used to insert elements into the container.
Stream iterators: These iterators are bound to input or output streams and can be used to iterate through the associated IO stream.
Reverse iterators: These iterators move backward, rather than forward.
Move iterators: These special-purpose iterators move rather than copy their elements.
10.4.1 Insert Iterators
An inserter is an iterator adaptor that takes a container and yields an iterator that adds elements to the specified container. When we assign a value through an insert iterator, the iterator calls a container operation to add an element at a specified position in the given container.
There are three kinds of inserters:
back_inserter creates an iterator that users
push_back
.front_inserter creates an iterator that uses
push_front
.inserter creates an iterator that use
insert
.
We can use front_inserter
only if the container has push_front
, we can use back_inserter
only if it has push_back
.
If it is an iterator generated by inserter, then an assignment such as *it = val
behaves as:
10.4.2 iostream Iterators
An istream_iterator reads an input stream, and an ostream_iterator writes an output steam.
The implementation is permitted to delay reading the stream until we use the iterator.
An ostream_iterator can be defined for any type that has an output operator. When we create an ostream_iterator, we may provide a second argument that specifies a character print to print following each element.
This program writes each element from vec
onto cout
following each element with a space. It is worth noting that we can omit the dereference and the increment when we assign to out_iter
. That is, we can write this loop equivalently as:
10.4.3 Reverse Iterators
A reverse iterator is an iterator that traverses a container backward, from the last element toward the first. A reverse iterator inverts the meaning of increment.
Reverse iterators require decrement operators.
10.5 Structure of Generic Algorithms
The iterator operations required by the algorithms are grouped into five iterator categories.
Input iterators: can read elements in a sequence. An input iterator must provide:
Equality and inequality operators (==, !=) to compare two iterators.
Prefix and postfix increment (++) to advance the iterator.
Dereference operator (*) to read an element; dereference may appear only on the right-hand side of an assignment.
The arrow operator (->) as a synonym for
(*it).member
, that is dereference the iterator and fetch a member from the underlying object.
Out iterators: can be thought of as having complementary functionality to input iterators; they write rather than read elements. Output iterators must provide:
Prefix and postfix increment (++) to advance the iterator.
Dereference (*), which may appear only as the left-hand side of an assignment.
Forward iterators: can read and write a given sequence. They move in only one direction through the sequence. Forward iterators support all the operations of both input iterators and output iterators.
Bidirectional iterators: can read and write a sequence forward or backward. In addition to supporting all the operations of a forward iterator, a bidirectional iterator also supports the prefix and postfix decrement (--) operators.
Random-access iterators: provide constant-time access to any position in the sequence. These iterators support all the functionality of bidirectional iterators.
The relation operators (<, <=, >, and >=) to compare the relative positions of two iterators.
Addition and subtraction operators (+, +=, -, and -=) on an iterator and an integral value.
The subtraction operator (-) when applied to two iterators, which yields the distance between two iterators.
The subscript operator (inter[n]) as synonym for
*(iter+n)
.
10.5.2 Algorithm Parameter Patterns
Most of the algorithms have one of the following four forms:
where alg
is the name of the algorithm, and beg
and end
denote the input range on which the algorithm operates.
A dest
parameter is an iterator that denotes a destination in which the algorithm can write its output. Algorithms that write to an output iterator assume the destination is large enough to hold the output.
Algorithms that take beg2
alone assume that the sequence beginning at beg2
is as large as the range denoted by beg
and end
.
10.5.3 Algorithm Naming Conventions
Separate from the parameter conventions, the algorithms also conform to a set of naming and overload conventions.
Algorithms that take an element value typically have a second named version that takes a predicate in place of the value.
10.6 Container-Specific Algorithms
The splice members
Last updated