Iterators Overview We have introduced, used, built, and studied iterators in several contexts, including List<>, TDeque<>, and TVector<>. We have seen that ordinary pointers also can be thought of as iterators for arrays. The importance of iterators for List<> was fairly obvious, from their introduction, since there is no other mechanism to access list elements. The importance of iterators for the other containers was perhaps less clear, until we encountered generic algorithms. Now we know that iterators provide a critical interface between generic algorithms and containers, thus enabling the re usability of code for both container and algorithm implementations. Iterators are perhaps the key concept making generic programming actually work. This chapter serves mainly to gather and organize information about iterators. Some new concepts are also introduced for completeness. Iterators are made so useful by giving them a uniform and predictable public interface with which algorithms may act as clients, while targeting specific containers through implementation details. For this reason, it is useful to classify iterators according to their public interface functionality, and it is also useful to prevent the number of categories from growing unmanageably large. C++ iterators may be classified into six categories according to their public interface functionality, as follows: input iterators output iterators forward iterators bidirectional iterators random access iterators adaptor iterators Of these we have so far only encountered bidirectional and random access iterators. These two categories are by far the most common. We will also introduce and use insert iterators, an example in the category of adaptor iterators, but most of the other categories we will leave for your study in the STL itself, as they pertain less to data structures and algorithms. Terminology Support Consistency of terminology is a critical feature for both containers and iterators. The mechanism used in the fsu template library is the typedef statement, which requires self discipline at the human level. The std template library uses the more reliable mechanism of traits, which are classes with no data and no methods, just typedefs, and from which iterator types inherit. Forward Iterators Forward iterators represent the most basic form of general purpose iterator. It is the only category of iterators guaranteed by the C++ standard to be supported by all of the container classes of the STL. (All of the containers in our class library actually support bidirectional iterators, but the vector container in the STL need not do so.) Forward iterators are those that have (at a minimum) all of the public interface shown in the slide. Note that these include tests for equality and non equality, element access via dereference, forward iteration, and all features associated with proper type. These are exactly the functionalities that enable the most basic kind of loop through a container, such as: Container C; Container::Iterator I; for (I = C.Begin(); I!= C.End(); ++I) cout << *I; These are also the functionalities assumed by all of the basic generic algorithms in tcpp/genalg.h. http://www.cs.fsu.edu/~lacher/courses/cop4530/lectures/iterators/script.html#link1 1/9
Bidirectional Iterators Bidirectional iterators are forward iterators with the addition of the two decrement operators to the public interface. The containers List<>, TDeque<>, and TVector<> support bidirectional iterators, and array pointers can also be considered bidirectional iterators. Therefore all of the basic generic algorithms of tcpp/genalg.h may be applied to these types of container, mix and match. Moreover, the generic algorithms of tcpp/gset.h may also be applied to such containers, provided that the containers meet the other assumptions required by these algorithms. Random Access Iterators Random access iterators are bidirectional iterators with the additional features of random access, which means a bracket operator and "pointer" arithmetic (as shown in the slide). The containers TDeque<> and TVector<>, as well as ordinary arrays, support random access iterators. Therefore the generic algorithms of tcpp/gbsearch.h and tcpp/gheap.h may be applied to these types of containers, provided that the other assumptions of the individual algorithms are met. ConstIterators Every iterator type that is associated with a container type also has a version called ConstIterator. A ConstIterator operates with the same functionality as Iterator, except that a ConstIterator does not allow the container to be mutated, or changed, in any way. This is manifested in code as follows: X::Iterator i; X::ConstIterator ci; t = *i; // OK t = *ci; // OK *i = t; // OK *ci = t; // ERROR: attempted use of ci to mutate container (change value at ci) Note the distinction between ConstIterator and const Iterator. The former is itself mutable (we can, for example, call operator ++) but may not be used to mutate the container into which it points. The latter is itself constant, so it would need to be initialized at the point of declaration and could never be changed. The main use of ConstIterator is inside any block where the associated container is constant. In such a block, it is an error to declare an iterator, but OK to declare a ConstIterator: template < typename T > bool IsIn (const List<T> list, T searchval) // List<T>::Iterator i; // ERROR: const environment List<T>::ConstIterator i; // OK: using ConstIterator for (i = list.begin(); i!= list.end(); ++i) // calls "const" versions of Begin, End if (searchval == *i) return true; return false; Note that this code uses the (Const)Iterator only to read from the container. The support for ConstIterators is built in to the container class. A way to implement ConstIterators is explained in the next slide. http://www.cs.fsu.edu/~lacher/courses/cop4530/lectures/iterators/script.html#link1 2/9
Container Support There is generally a tight coupling between a container and its associated iterator type (via declaration of mutual friendship). Moreover, for the container/iterator system to function appropriately there must be support for both terminology and ititialization iterators built in to the container. Terminology Support is illustrated in this code snippet (part of the definition of the container class X): template < typename T > class X... typedef typename I::Iterator typedef ConstRAIterator<I>... Iterator; ConstIterator; Initialization support is supplied via the Begin/End method pairs:... Iterator Begin(); ConstIterator Begin() const; Iterator End(); ConstIterator End() const;... Note the use of the const modifier to distinguish between the overloads of Begin() and End(). Input Iterators Input iterators are specialized toward the purpose of reading data from a source such as an istream where elements can be read but not written, and once passed they cannot be read again. Input iterators can be associated with istreams using adaptors. Input iterators have a dereference operator that is not fully functional in that it can only be used to read a value, not to write a value. In other words, if I is an input iterator, then v = *I; // OK for input iterators is a legal statement but *I = v; // not OK for input iterators is not. (That is, *I may be an rvalue but not an lvalue: it may appear only on the right side of an assignment.) This restriction might be enforced by returning a constant reference instead of a reference by the dereference operator, as illustrated in the slide. Note that this interface is barely enough to serve as the first iterator type for a generic algorithm such as g_copy() (copied here from tcpp/genalg.h): template <class I, class J> void g_copy (I source_beg, I source_end, J dest_beg) while (source_beg!= source_end) *dest_beg++ = *source_beg++; http://www.cs.fsu.edu/~lacher/courses/cop4530/lectures/iterators/script.html#link1 3/9
Output Iterators Output iterators are specialized toward the purpose of writing data to a source such as an ostream or a container where elements can be written but not read, and once passed they cannot be written again. Output iterators can be associated with ostreams using adaptors. Insert iterators, defined below, are output iterators associated with containers. Output iterators have a dereference operator that is not fully functional in that it can only be used to write a value,but not to read a value. In other words, if I is an output iterator, then v = *I; // not OK for output iterators is a not legal statement but *I = v; // OK for output iterators is. (That is, *I may be an lvalue but not an rvalue: it may appear only on the left side of an assignment.) This restriction is enforced by the semantics of the particular output iterator. A typical case is illustrated in the slide, where the return value of operator *() is a (self)reference rather than a reference to a value. Note that this interface is barely enough to serve as the second iterator type for a generic algorithm such as g_copy(). The Iterator Hierarchy This slide illustrates the various iterator categories and how they relate among themselves. Not that these are NOT inheritance relationships in the usual sense of object oriented programming. They are inheritance relationships in the higher sense of patterns, which are often enforced by maintaining intellectual control of a development project. Pattern inheritance can also be enforced by inheritance of "traits", which are essentially classes with no data or functionality, only terminology defined using public typedef statements. The former approach is taken in this course. The latter approach is taken by the C++ STL. Iterator Adaptors Iterator adaptors are iterator classes obtained as adaptor classes of other classes. Suprisingly, sometimes the adaptee class is not an iterator class. ConstIterator ReverseIterator Insert Iterators Stream iterators are iterators adapted from stream classes and facilitate reading from/writing to streams with generic algorithms. Reverse iterators are adapted from forward iterators and serve to re define forward incrementation to be backward incrementation (i.e., decrementation). While not difficult subjects, we will not cover these topics in detail due to there minimal relevance to data structures and algorithms. ConstIterator Adaptor One way to implement ConstIterators for various containers is to define a single adaptor class and use it to adapt all of the container iterator classes. The idea is to just omit the non const versions of the dereference operator and bracket operator. The adaptor is defined for random access iterators, but can be http://www.cs.fsu.edu/~lacher/courses/cop4530/lectures/iterators/script.html#link1 4/9
used for any other iterator type. // // ConstRAIterator<I> definition // template < class I > class ConstRAIterator public: // terminology support typedef typename I::ValueType ValueType; typedef typename I::PointerType PointerType; typedef typename I::ConstPointerType ConstPointerType; typedef typename I::ReferenceType ReferenceType; typedef typename I::ConstReferenceType ConstReferenceType; typedef typename I::ContainerType ContainerType; typedef typename I::Iterator Iterator; typedef ConstRAIterator<I> ConstIterator; // constructors ConstRAIterator (); ConstRAIterator (const ConstRAIterator& i); // copy constructor ConstRAIterator (const I& i); // type converter bool operator == (const ConstIterator& i2) const; bool operator!= (const ConstIterator& i2) const; ConstReferenceType operator * () const; // return element as R value ConstReferenceType operator [] (size_t index) const; // return element as R value ConstIterator& operator = (const ConstIterator & i); ConstIterator& operator ++ (); // prefix increment ConstIterator operator ++ (int); // postfix ConstIterator& operator (); // prefix decrement ConstIterator operator (int); // postfix // "pointer" arithmetic long operator (const ConstIterator & i2) const; // these are template member operators for pointer arithmetic ConstIterator operator + (N n) const; ConstIterator& operator += (N n); ConstIterator& operator = (N n); protected: I i_; ; // // ConstRAIterator<I> implementations // ConstRAIterator<I>::ConstRAIterator () : i_() http://www.cs.fsu.edu/~lacher/courses/cop4530/lectures/iterators/script.html#link1 5/9
ConstRAIterator<I>::ConstRAIterator (const ConstRAIterator& i) : i_(i.i_) ConstRAIterator<I>::ConstRAIterator (const I& i) : i_(i) bool ConstRAIterator<I>::Valid () const return i_.valid(); bool ConstRAIterator<I>::operator == (const ConstRAIterator& i2) const return i_ == i2.i_; bool ConstRAIterator<I>::operator!= (const ConstRAIterator& i2) const return i_!= i2.i_; typename I::ConstReferenceType ConstRAIterator<I>::operator * () const return *i_; typename I::ConstReferenceType ConstRAIterator<I>::operator [] (size_t index) const return i_[index]; ConstRAIterator<I>& ConstRAIterator<I>::operator = (const ConstRAIterator & i) i_ = i.i_; ConstRAIterator<I>& ConstRAIterator<I>::operator ++ () ++i_; ConstRAIterator<I> ConstRAIterator<I>::operator ++ (int) ConstIterator i(*this); http://www.cs.fsu.edu/~lacher/courses/cop4530/lectures/iterators/script.html#link1 6/9
operator ++(); return i; ConstRAIterator<I>& ConstRAIterator<I>::operator () i_; ConstRAIterator<I> ConstRAIterator<I>::operator (int) ConstIterator i(*this); operator (); return i; long ConstRAIterator<I>::operator (const ConstRAIterator<I> & i2) const return i_ i2.i_; ConstRAIterator<I> ConstRAIterator<I>::operator + (N n) const ConstIterator i = *this; i.i_ += n; return i; ConstRAIterator<I>& ConstRAIterator<I>::operator += (N n) i_ += n; ConstRAIterator<I>& ConstRAIterator<I>::operator = (N n) i_ = n; // useage: template < typename T > bool IsIn (const List<T> & list, T searchval) List<T>::ConstIterator i; for (i = list.begin(); i!= list.end(); ++i) // calls "const" versions of Begin, End if (searchval == *i) return true; return false; http://www.cs.fsu.edu/~lacher/courses/cop4530/lectures/iterators/script.html#link1 7/9
Note that this one adaptor can be used for bidirectional and forward iterators because of the fact: template function code is compiled only when it is called in the program under compilation. Thus the random access features will be ignored when adapting bidirectional iterators and in addition the decrement operations will be ignored when adapting forward iterators. Insert Iterator Adaptors An important example of iterator adaptors, especially for use in the study of containers, is the insert iterator adaptor class. There are three versions, each of which overloads operator =() as an inserter into a container. Consider the following class definition: class PushBackIterator public: explicit PushBackIterator (C& x) : Cptr(&x) PushBackIterator <C>& operator = (const typename C::value_type& t) Cptr > PushBack(t); PushBackIterator<C>& operator * () PushBackIterator<C>& operator ++ () PushBackIterator<C>& operator ++ (int) protected: C* Cptr_; ; This class uses a container C with a PushBack() method to create an iterator that can do only one thing use operator *() as an lvalue to insert items at the back of the container. Note that this is actually accomplished by effectively removing functionality from operator *() and overloading the assignment operator. The operator *() and the increment operators are overloaded to perform no function other than to return a self reference. The following usage declares a list L and a PushBackIterator associated with L: List < char > L; PushBackIterator < List < char > > Litr(L); and the call g_copy(v.begin(), V.End(), Litr); has the effect of inserting all of the elements of the vector V to the back of the list L. The declaration of such a PushBackIterator is somewhat cumbersome, since it must be associated with a specific container as well as declared as to type. After the initial declaration, the following template function provides a useful shortcut to repeating the second line in the declaration above just to reassociate the iterator with a particular container object. It returns a PushBackIterator associated with the container. PushBackIterator<C> BackPusher (C& x) return PushBackIterator <C> (x); http://www.cs.fsu.edu/~lacher/courses/cop4530/lectures/iterators/script.html#link1 8/9
// useage: Litr = BackPusher(L); The declaration and use of PushBackIterator adaptors is illustrated in the programs tests/fga.cpp and tests/fgset.cpp. Similar definitions hold for PushFrontIterator and InsertIterator adaptors, with these prototypes: class PushFrontIterator; PushFrontIterator<C> FrontPusher (C& x); class InsertIterator; InsertIterator<C> Inserter (C& x); All three insert iterator adaptor classes and supporting functions are given in tcpp/cinsert.h. http://www.cs.fsu.edu/~lacher/courses/cop4530/lectures/iterators/script.html#link1 9/9