generate(vs.begin(), vs.end(), NumStringGen());
copy(vs.begin(), vs.end(),
ostream_iterator<string>(cout, "\t"));
cout << endl;
const char* vcp[sz];
transform(vs.begin(), vs.end(), vcp,
mem_fun_ref(&string::c_str));
vector<double> vd;
transform(vcp, vcp + sz, back_inserter(vd),
std::atof);
copy(vd.begin(), vd.end(),
ostream_iterator<double>(cout, "\t"));
cout << endl;
} ///:~
This program does two transformations: one to convert strings to C-style strings (arrays of characters), and one to convert the C-style strings to numbers via atof( ). It would be nice to combine these two operations into one. After all, we can compose functions in mathematics, so why not C++?.
The obvious approach takes the two functions as arguments and applies them in the proper order:
//: C06:ComposeTry.cpp
// A first attempt at implementing function composition
#include <cassert>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <string>
using namespace std;
template<typename R, typename E, typename F1, typename F2>
class unary_composer {
F1 f1;
F2 f2;
public:
unary_composer(F1 fone, F2 ftwo) : f1(fone), f2(ftwo) {}
R operator()(E x) {
return f1(f2(x));
}
};
template<typename R, typename E, typename F1, typename F2>
unary_composer<R, E, F1, F2> compose(F1 f1, F2 f2) {
return unary_composer<R, E, F1, F2>(f1, f2);
}
int main()
{
double x =
compose<double, const string&>(atof,
mem_fun_ref(&string::c_str))("12.34");
assert(x == 12.34);
} ///:~
The unary_composer object in this example stores the function pointers atof and string::c_str such that the latter function is applied first when its operator( ) is called. The compose( ) function adapter is a convenience, so we don’t have to supply all four template arguments explicitly—F1 and F2 are deduced from the call.
It would be much better, of course, if we didn’t have to supply any template arguments at all. This is achieved by adhering to the convention for type definitions for adaptable function objects; in other words, we will assume that the functions to be composed are adaptable. This requires that we use ptr_fun( ) for atof( ). For maximum flexibility, we also make unary_composer adaptable in case it gets passed to a function adapter. The following program does so and easily solves the original problem.
//: C06:ComposeFinal.cpp
// An adaptable composer
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include "NumStringGen.h"
using namespace std;
template<typename F1, typename F2>
class unary_composer
: public unary_function<typename F2::argument_type,
typename F1::result_type> {
public:
unary_composer(F1 f1, F2 f2) : f1(f1), f2(f2) {}
typename F1::result_type
operator()(typename F2::argument_type x) {
return f1(f2(x));
}
private:
F1 f1;
F2 f2;
};
template<typename F1, typename F2>
unary_composer<F1, F2> compose(F1 f1, F2 f2) {
return unary_composer<F1, F2>(f1, f2);
}
int main() {
const int sz = 9;
vector<string> vs(sz);
// Fill it with random number strings:
generate(vs.begin(), vs.end(), NumStringGen());
copy(vs.begin(), vs.end(),
ostream_iterator<string>(cout, "\t"));
cout << endl;
vector<double> vd;
transform(vs.begin(), vs.end(), back_inserter(vd),
compose(ptr_fun(atof), mem_fun_ref(&string::c_str)));
copy(vd.begin(), vd.end(),
ostream_iterator<double>(cout, "\t"));
cout << endl;
} ///:~
Once again we must use typename to let the compiler know that the member we are referring to is a nested type.
Some implementations[85] support composition of function objects as an extension, and the C++ standards committee is likely to add these capabilities to the next version of standard C++.
A catalog of STL algorithms
This section provides a quick reference for when you’re searching for the appropriate algorithm. We leave the full exploration of all the STL algorithms to other references (see the end of this chapter, and Appendix A), along with the more intimate details of performance, and so on. Our goal here is for you to become rapidly comfortable and facile with the algorithms, and we’ll assume you will look into the more specialized references if you need more depth of detail.
Although you will often see the algorithms described using their full template declaration syntax, we’re not doing that here because you already know they are templates, and it’s quite easy to see what the template arguments are from the function declarations. The type names for the arguments provide descriptions for the types of iterators required. We think you’ll find this form is easier to read, and you can quickly find the full declaration in the template header file if for some reason you feel the need.
The reason for all the fuss about iterators is to accommodate any type of container that meets the requirements in the standard library. So far we have illustrated the generic algorithms with only arrays and vectors as sequences, but in the next chapter you’ll see a broad range of data structures that support less robust iteration. For this reason, the algorithms are categorized in part by the types of iteration facilities they require.
The names of the iterator classes describe the iterator type to which they must conform. There are no interface base classes to enforce these iteration operations—they are just expected to be there. If they are not, your compiler will complain. The various flavors of iterators are described briefly as follows.
InputIterator. An input iterator only allows reading elements of its sequence in a single, forward pass using operator++ and operator*. Input iterators can also be tested with operator== and operator!=. That’s all.
OutputIterator. An output iterator only allows writing elements to a sequence in a single, forward pass using operator++ and operator*. OutputIterators cannot be tested with operator== and operator!=, however, because you assume that you can just keep sending elements to the destination and that you don’t have to see if the destination’s end marker was reached. That is, the container that an OutputIterator references can take an infinite number of objects, so no end-checking is necessary. This requirement is important so that an OutputIterator can be used with ostreams (via ostream_iterator), but you’ll also commonly use the "insert" iterators such as are the type of iterator returned by back_inserter( )).