42.4 So . . . ?
One option might be to default the second endpoint iterator to I(), as follows:
template <. . .> class filter_iterator { . . . public: // Construction filter_iterator(I it, P pr, I end = I()) : m_it(it) , m_end(end) , m_pr(pr) { . . . private: // Member Variables I m_it; I m_end; P m_pr; };
However, this relies on the iterator type defining a default-constructed iterator as being equivalent to the endpoint iterator. Though this would work, on a case-by-case basis, for some iterators, including readdir_sequence::const_iterator (Section 19.3) and findfile_sequence::const_iterator (Section 20.5), it would not work for others, such as glob_sequence::const_iterator (Section 17.3). Or, if you prefer, it might work for std::list, std::deque, std::map, but it can't work for std::vector and, importantly, pointers.
Furthermore, providing this facility puts the onus on the user to know whether the assumption holds for a given iterator, which is both unreasonable and exceedingly likely to lead to failures. More leaking abstractions! Add the fact that such failures may never exhibit in testing, instead lurking until your product is out in the field, and this option is totally unacceptable.
"Wait!" you might say doggedly, "We can specialize the creator function to reject pointers." And so we can. However, there are plenty of iterators that are not pointers that do not satisfy the default-constructor/endpoint equivalence. For one, a random access iterator that is not a pointer will not do so.
Or you might wonder, "Can't we specialize to reject random access iterators?" Indeed, that would help, were it not for the fact that many of the iterators fulfilling other categories will also fail. In short, there's no getting away from the following rule and tip.
With this in mind, let's see how to define a robust filtering iterator component.