838. Can an end-of-stream iterator become a non-end-of-stream one?

Section: 27.6.1 [istream.iterator] Status: C++11 Submitter: Martin Sebor Opened: 2008-05-17 Last modified: 2016-02-10

Priority: Not Prioritized

View all other issues in [istream.iterator].

View all issues with C++11 status.

Discussion:

From message c++std-lib-20003...

The description of istream_iterator in 27.6.1 [istream.iterator], p. 1 specifies that objects of the class become the end-of-stream (EOS) iterators under the following condition (see also issue 788 another problem with this paragraph):

If the end of stream is reached (operator void*() on the stream returns false), the iterator becomes equal to the end-of-stream iterator value.

One possible implementation approach that has been used in practice is for the iterator to set its in_stream pointer to 0 when it reaches the end of the stream, just like the default ctor does on initialization. The problem with this approach is that the Effects clause for operator++() says the iterator unconditionally extracts the next value from the stream by evaluating *in_stream >> value, without checking for (in_stream == 0).

Conformance to the requirement outlined in the Effects clause can easily be verified in programs by setting eofbit or failbit in exceptions() of the associated stream and attempting to iterate past the end of the stream: each past-the-end access should trigger an exception. This suggests that some other, more elaborate technique might be intended.

Another approach, one that allows operator++() to attempt to extract the value even for EOS iterators (just as long as in_stream is non-0) is for the iterator to maintain a flag indicating whether it has reached the end of the stream. This technique would satisfy the presumed requirement implied by the Effects clause mentioned above, but it isn't supported by the exposition-only members of the class (no such flag is shown). This approach is also found in existing practice.

The inconsistency between existing implementations raises the question of whether the intent of the specification is that a non-EOS iterator that has reached the EOS become a non-EOS one again after the stream's eofbit flag has been cleared? That is, are the assertions in the program below expected to pass?

   sstream strm ("1 ");
   istream_iterator eos;
   istream_iterator it (strm);
   int i;
   i = *it++
   assert (it == eos);
   strm.clear ();
   strm << "2 3 ";
   assert (it != eos);
   i = *++it;
   assert (3 == i);
     

Or is it intended that once an iterator becomes EOS it stays EOS until the end of its lifetime?

[ San Francisco: ]

We like the direction of the proposed resolution. We're not sure about the wording, and we need more time to reflect on it,

Move to Open. Detlef to rewrite the proposed resolution in such a way that no reference is made to exposition only members of istream_iterator.

[ 2009-07 Frankfurt: ]

Move to Ready.

Proposed resolution:

The discussion of this issue on the reflector suggests that the intent of the standard is for an istreambuf_iterator that has reached the EOS to remain in the EOS state until the end of its lifetime. Implementations that permit EOS iterators to return to a non-EOS state may only do so as an extension, and only as a result of calling istream_iterator member functions on EOS iterators whose behavior is in this case undefined.

To this end we propose to change 27.6.1 [istream.iterator], p1, as follows:

The result of operator-> on an end-of-stream is not defined. For any other iterator value a const T* is returned. Invoking operator++() on an end-of-stream iterator is undefined. It is impossible to store things into istream iterators...

Add pre/postconditions to the member function descriptions of istream_iterator like so:

istream_iterator();

Effects: Constructs the end-of-stream iterator.
Postcondition: in_stream == 0.

istream_iterator(istream_type &s);

Effects: Initializes in_stream with &s. value may be initialized during construction or the first time it is referenced.
Postcondition: in_stream == &s.

istream_iterator(const istream_iterator &x);

Effects: Constructs a copy of x.
Postcondition: in_stream == x.in_stream.

istream_iterator& operator++();

Requires: in_stream != 0.
Effects: *in_stream >> value.

istream_iterator& operator++(int);

Requires: in_stream != 0.
Effects:

istream_iterator tmp (*this);
*in_stream >> value;
return tmp;