120 likes | 147 Views
From Last Time: Search with Generic Iterators. Third generalization: separate iterator type parameter We arrive at the find algorithm (Austern pp. 13): template < typename Iterator, typename T> Iterator find ( Iterator first, Iterator last, const T & value) {
E N D
From Last Time: Search with Generic Iterators • Third generalization: separate iterator type parameter • We arrive at the find algorithm (Austern pp. 13): template <typename Iterator,typename T> Iterator find (Iterator first,Iterator last, const T & value) { while (first != last && *first != value) ++first; return first; } • Which kinds of iterators will work with this algorithm? • How can we determine that from the algorithm itself?
Key Ideas: Concepts and Models • A concept gives a set of type requirements • Classify/categorize types (e.g., random access iterators) • Tells whether or not a type can or cannot be used with a particular algorithm (get a compiler error if it cannot) • E.g., in the examples from last time, we could not use a linked list iterator in find1 or even find2, but we can use one in find • Any specific type that meets the requirements is a model of that concept • E.g., list<char>::iterator vs. char *in find • Different abstractions (bi-linked list iterator vs. char array iterator) • No inheritance-based relationship between them • But both model iterator concept necessary for find
Concepts and Models, Continued • What very basic concept does the last statement of find, (the line return first;) assume? • Asked another way, what must be able to happen to first when it’s returned from function find? • Same requirement imposed by by-value iterator parameters • What other capabilities are required of the Iterator and T type parameters by the STL find algorithm ? template <typename Iterator, typename T> Iterator find (Iterator first, Iterator last, const T & value) { while (first != last && *first != value) ++first; return first; }
Matching an Algorithm to the Iterators it Needs What STL iterator category does findrequire?
Iterator Concept Hierarchy “transient” write to stream (ostream) “destructive” read at head of stream (istream) • read or write a value (one-shot) Input Iterator Output Iterator Singly-inked-list style access (forward_list) • value persists after read/write • values have locations • can express distancebetween two iterators Forward Iterator Bi-linked-list style access (list) Bidirectional Iterator is-a (refines) Array/buffer style access (vector, deque) Random Access Iterator
What if an Algorithm Has Alternative Versions? // Based on Austern, pp. 38, 39 template <class Iter, class Distance> void move (Iter i, Distance d, fwd) { while (d>0) {--d; ++i;} // O(d) } template <class Iter, class Distance> void move (Iter i, Distance d, rand) { i += d; // O(1) } template <class Iter, class Distance> void move (Iter i, Distance d) { move (i, d, iterator_traits<Iter>:: iterator_category() ) } • Static dispatching • Implementations provide different signatures • Iterator type is evaluated at compile-time • Links to the best implementation • Notice how type tags are used concrete tag (empty struct) type concrete tag (empty struct) type default constructor (call syntax)
Iterator Traits and Category Type Tags struct input {}; // empty structs for type tags struct output {}; struct fwd : public input {}; // note inheritance struct bidir : public fwd {}; struct rand : public bidir {}; template <typename I> struct iterator_traits { ... typedef typename I::iterator_category iterator_category; }; template <typename T> struct iterator_traits<T*> { ... typedef rand iterator_category; }; template <typename T> struct iterator_traits<const T*> { ... typedef rand iterator_category; }; • Need a few concrete types to use as tags • E.g., empty structs • E.g., input, output, fwd, bidir, and rand • Tags provide yet another associated type for iterators • Iterator category • Again, made available by using the traits idiom (actually, random_access_iterator_tag)
Can Extend STL Algorithms with Callable Objects • Make the algorithms even more general • Can be used parameterize policy • E.g., the order produced by a sorting algorithm • E.g., the order maintained by an associative containe • Each callable object does a single, specific operation • E.g., returns true if first value is less than second value • Algorithms often have overloaded versions • E.g., sort that takes two iterators (uses operator<) • E.g., sort that takes two iterators and a binary predicate, uses the binary predicate to compare elements in range
Callable Objects and Adapters • Callable objectssupportfunction call syntax • A function or function pointer bool (*PF) (const string &, const string &); // function pointer bool string_func (const string &, const string &); // function • A struct or class providing an overloaded operator() struct strings_ok { bool operator() (const string &s, const string &t) { return (s != “quit”) && (t != “quit”); } }; • A lambda expression (unnamed inline function) [quit_string] (const string &s, const string &t) -> bool {return (s != quit_string) && (t != quit_string);} • Adapters further extend callable objects • E.g., bind any argument using bind and _1 _2 _3 etc. • E.g., wrap a member function using mem_fn • E.g., wrap callable object with function (associates types)
Using Functions with an Algorithm #include <iostream> #include <vector> #include <string> #include <iterator> #include <algorithm> using namespace std; struct Employee { Employee (const char * n, int i) : name_(n), id_(i) {} string name_; int id_; }; typedef Employee * EmployeePtr; ostream& operator<< (ostream & os, const EmployeePtr & e) { os << e->name_ << " " << e->id_ << " "; return os; } // function for comparing EmployeePtrs bool id_compare (const EmployeePtr & e, const EmployeePtr & f) { return e->id_< f->id_|| (e->id_ == f->id_ && e->name_ < f->name_); } int main (int, char *[]) { vector<EmployeePtr> v; v.push_back(new Employee("Claire", 23451)); v.push_back(new Employee("Bob", 12345)); v.push_back(new Employee("Alice", 54321)); cout << "v: " ; copy (v.begin(), v.end(), ostream_iterator<EmployeePtr>(cout)); cout << endl; // "v: Claire 23451 Bob 12345 Alice 54321 " sort (v.begin(), v.end(), id_compare); cout << "v: " ; copy (v.begin(), v.end(), ostream_iterator<EmployeePtr>(cout)); cout << endl; // "v: Bob 12345 Claire 23451 Alice 54321 " // clean up: pointers "own" the heap objects for (vector<EmployeePtr>::iterator i = v.begin(); i != v.end(); ++i) { delete *i; } return 0; } heap object pass function name
count_if algorithm Generalizes the count algorithm Instead of comparing for equality to a value Applies a given predicate function object (functor) If functor’s result is true, increases count #include <iostream> #include <vector> #include <algorithm> using namespace std; template <typename T> // covers many types struct odd { bool operator() (T t) const { return (t % 2) != 0; } }; int main (int, char * []) { vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(2); cout << "there are " << count_if(v.begin(), v.end(), odd<int>()) << " odd numbers in v" << endl; return 0; } Using Function Objects with an Algorithm /* output is there are 2 odd numbers in v */
Concluding Remarks • STL algorithms give you useful, generic functions • Combine easily with a variety of containers/iterators • Support many common data structure manipulations • Finding and modifying values, re-ordering, numeric operations • Reusing them saves you from writing/debugging code • Many STL algorithms can be extended • Especially by plugging callable objects (functors) into them • C++11 lambdas & the bind function give new ways to do that • Think about how iterators and algorithms combine • Think about which category of iterator a container provides • Think about the concept each iterator models • Think about the type requirements an algorithm imposes • Think about which combinations are valid, accordingly