42.2 An Invalid Version
How would this work? Naturally, filter() will be a creator function that returns an instance of a (suitably specialized) filtering iterator type. We might imagine an iterator class template such as that shown in Listing 42.1.
Listing 42.1. Invalid Version of filter_iterator
template< typename I // The adapted iterator , typename P // Unary predicate that will select items , typename T = adapted_iterator_traits<I> > class filter_iterator { public: // Member Types typedef I base_iterator_type; typedef P filter_predicate_type; typedef T traits_type; typedef filter_iterator<I, P, T> class_type; typedef typename traits_type::iterator_category iterator_category; typedef typename traits_type::value_type value_type; . . . // And so on, for usual members (from adapted_iterator_traits) public: // Construction filter_iterator(I it, P pr) : m_it(it) , m_pr(pr) { for(; !m_pr(*m_it); ++m_it) // Get first "selected" position {} } public: // Forward Iterator Methods class_type& operator ++() { for(++m_it; !m_pr(*m_it); ++m_it) // Advance, then get next pos {} return *this; } class_type operator ++(int); // Usual implementation reference operator *(); // Usual implementation const_reference operator *() const; // Usual implementation private: // Member Variables I m_it; P m_pr; };
Alas, the statement outputting read-only files shown in Section 42.1 will fail, probably in a crash. In fact, just about any use of this iterator will fail. There are two problems.
First, in the constructor for the first iterator, the active iterator, it uses the predicate and increment operator to ensure that the filter_iterator instance has the correct position before it is used. This correct position is the first one that matches the predicate, and that may be outside the given range [files.begin(), files.end()).
Second, the constructor for the second iterator, the one that adapts the endpoint iterator, dereferences its base iterator instance. It's a strict part of the STL iterator concept (Section 1.3) that we can "never [assume] that past-the-end values are dereferenceable" (C++-03: 24.1;5). (This also means that the implementation of operator *() is not well defined, but that's moot because we would have to go through an undefined constructor to get to a point where it could be invoked.)