Professional C__ - Marc Gregoire [232]
Writing Your Own Function Objects
If your compiler does not yet support the C++11 lambda expressions, you can write your own function objects to perform more specific tasks than those provided by the predefined functors. If you want to be able to use the function adapters with these functors, you must supply certain typedefs. The easiest way to do that is to subclass your function object classes from either unary_function or binary_function, depending on whether they take one or two arguments. These two classes, defined in class myIsDigit : public unary_function { public: bool operator() (char c) const { return ::isdigit(c); } }; bool isNumber(const string& str) { auto end = str.end(); auto it = find_if(str.begin(), end, not1(myIsDigit())); return (it == end); } Code snippet from FunctionObjects\WritingFunctionObject.cpp Note that the overloaded function call operator of the myIsDigit class must be const in order to pass objects of it to find_if(). The algorithms are allowed to make multiple copies of function object predicates and call different ones for different elements. The function call operator needs to be const, thus, you cannot write functors such that they count on any internal state to the object being consistent between calls. Before C++11, a class defined locally in the scope of a function could not be used as a template argument. C++11 removes this limitation. The following example is perfectly legal in C++11, but was not legal before C++11: bool isNumber(const string& str) { class myIsDigit : public unary_function { public: bool operator() (char c) const { return ::isdigit(c); } }; auto end = str.end(); auto it = find_if(str.begin(), end, not1(myIsDigit())); return (it == end); } Code snippet from FunctionObjects\WritingFunctionObjectLocal.cpp As you can see from these examples, C++11 lambda expressions allow you to write more readable and more elegant code. We recommend to use simple lambda expressions instead of function objects, and to use function objects only when they need to do more complicated things. ALGORITHM DETAILS There are five types of iterators: input, output, forward, bidirectional, and random-access. These are described in Chapter 12. There is no formal class hierarchy of these iterators, because the implementations for each container are not part of the standard hierarchy. However, one can deduce a hierarchy based on the functionality they are required to provide. Specifically, every random access iterator is also bidirectional, every bidirectional iterator is also forward, and every forward iterator is also input and output. Figure 13-1 shows such hierarchy. Dotted lines are used because the figure is not a real class hierarchy. FIGURE 13-1 The standard way for the algorithms to specify what kind of iterators they need is to use the following names for the iterator template arguments: InputIterator, OutputIterator, ForwardIterator, BidirectionalIterator, and RandomAccessIterator. These names are just names: They don’t provide binding type checking. Therefore, you could, for example, try to call an algorithm expecting a RandomAccessIterator by passing a bidirectional iterator. The template doesn’t do type checking, so it would allow this instantiation. However, the code in the function that uses the random access iterator capabilities would fail to compile on the bidirectional iterator. Thus, the requirement is enforced, just not where you would expect. The error message can therefore be somewhat confusing. For example, attempting to use the generic sort() algorithm, which requires a random
This chapter describes the general categories of algorithms, with examples of each. The Standard Library Reference resource on the website (www.wrox.com) contains a summary of all the algorithms.