To my knowledge, the hierarchy of iterator categories goes like this:
Random access -> Bi-directional -> Forward -> Input
-> Output
Correct?
I always thought there was a rule, that if an algorithm expects a particular type of iterator, you can provide iterators of categories up the chain, but not down. So I was reading this answer, where ildjarn suggests suggested (then later corrected himself) using std::ifstream
with std::istream_iterator
and std::search
to find data in a in a file. I was about to comment that you can't do that, because search
expects Forward iterators, and istream_iterator
is an Input iterator. But just to make sure, I tried this:
std::istringstream iss("Elephant hats for sale.");
std::istream_iterator<char> begin(iss), end;
std::string sub("hat");
auto i = std::search(begin, end, sub.begin(), sub.end());
I didn't expect it to compile, but it did. However, the results seem to be useless because if I follow it with this:
while(i != end)
{
std::cout << *i;
++i;
}
There is no output. So, my question is this: Is my compiler in error for allowing my call to search
using istream_iterator
? Or are there no rules preventing this sort of thing?
Can input iterators be used where forward iterators are expected?
No. The difference between an input iterator and a forward iterator is that an input iterator is a "single-pass" iterator but a forward iterator is a "multi-pass" iterator.
Once you advance an input iterator, you can no longer access the previous elements in the range. If you make a copy of an input iterator, both iterators remain valid until you advance one of them; then the other ceases to be valid.
With a forward iterator, you can iterate over the sequence any number of times, you can have multiple usable copies of an iterator at once, you can use multiple iterators into the sequence at the same time, and you can dereference an iterator as many times as you'd like before advancing it again.
So, my question is this: Is my compiler in error for allowing my call to search using
istream_iterator
?
There is no rule that the compiler must reject the code.
The rule is that you must be sure to pass the right type of iterator that is required by the function. Sometimes if you pass the wrong type of iterator you get a compilation error. Sometimes the program will compile but will not function correctly. Sometimes things will appear to work correctly. The results are undefined if you violate the requirements of calling the function.
Generic algorithms usually impose requirements on their type parameters by assuming that the type arguments provided actually meet the requirements. So, for example, an algorithm that only works with random access iterators will "enforce" this requirement by performing some operation that only works with random access iterators (e.g. it + 1
). If the iterator doesn't support that operation (operator+(iterator, int)
here), the code will fail to compile.
The problem is that there is no way to distinguish between input iterators and forward iterators this way: you can increment and dereference both of them; the difference is in how many times you can perform each of those operations and the sequence in which you can perform those operations. So, an algorithm like std::search
will use *it
and ++it
, which will "work" just fine for input iterators, at least insofar as the code will compile.
In theory, an algorithm could use the std::iterator_traits
class template to determine whether an iterator is an input iterator or a forward iterator; I don't know whether that would be permitted by the C++ language standard. If the library did that, you could get a compilation error for your code, which would be better.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With