Home > Articles > Programming > C/C++

  • Print
  • + Share This
This chapter is from the book

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.)

  • + Share This
  • 🔖 Save To Your Account