This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

2366. istreambuf_iterator end-of-stream equality

Section: 25.6.4 [istreambuf.iterator] Status: New Submitter: Hyman Rosen Opened: 2014-02-19 Last modified: 2023-04-13

Priority: 3

View other active issues in [istreambuf.iterator].

View all other issues in [istreambuf.iterator].

View all issues with New status.

Discussion:

Given the following code,

#include <sstream>

std::stringbuf buf;
std::istreambuf_iterator<char> begin(&buf);
std::istreambuf_iterator<char> end;

it is not clear from the wording of the Standard whether begin.equal(end) must be true. In at least one implementation it is not (CC: Sun C++ 5.10 SunOS_sparc Patch 128228-25 2013/02/20) and in at least one implementation it is (gcc version 4.3.2 x86_64-unknown-linux-gnu).

25.6.4 [istreambuf.iterator] says that end is an end-of-stream iterator since it was default constructed. It also says that an iterator becomes equal to an end-of-stream iterator when end of stream is reached by sgetc() having returned eof(). [istreambuf.iterator::equal] says that equal() returns true iff both iterators are end of stream or not end of stream. But there seems to be no requirement that equal check for end-of-stream by calling sgetc().

Jiahan Zi at BloombergLP discovered this issue through his code failing to work correctly. Dietmar Kühl has opined in a private communication that the iterators should compare equal.

[2023-03-31; Jonathan Wakely comments]

I agree that they should compare equal, but that's in conflict with the resolution of LWG 2544, which says that begin must not be at end-of-stream because &buf is not null.

[2023-04-12; Jonathan adds wording]

Proposed resolution:

This wording is relative to N4944.

  1. Change 25.6.4.1 [istreambuf.iterator.general] as indicated:

    
        constexpr istreambuf_iterator() noexcept;
        constexpr istreambuf_iterator(default_sentinel_t) noexcept;
        istreambuf_iterator(const istreambuf_iterator&) noexcept = default;
        ~istreambuf_iterator() = default;
        istreambuf_iterator(istream_type& s) noexcept;
        : istreambuf_iterator(s.rdbuf()) { }
        istreambuf_iterator(streambuf_type* s) noexcept;
        istreambuf_iterator(const proxy& p) noexcept;
        
    
      private:
        streambuf_type* sbuf_;              // exposition only
        int_type c_{};                      // exposition only
      };
    }
    
  2. Change 25.6.4.3 [istreambuf.iterator.cons] as indicated:

    For each istreambuf_iterator constructor in this section, an end-of-stream iterator is constructed if and only if the exposition-only member sbuf_ is initialized with a null pointer value or if sbuf_->sgetc() returns traits_type::eof().

    constexpr istreambuf_iterator() noexcept;
    constexpr istreambuf_iterator(default_sentinel_t) noexcept;
    

    -1- Effects: Initializes sbuf_ with nullptr.

    istreambuf_iterator(istream_type& s) noexcept;

    -2- Effects: Initializes sbuf_ with s.rdbuf().

    istreambuf_iterator(streambuf_type* s) noexcept;
    

    [Drafting note: sgetc() can throw, but this function is noexcept. Should it swallow exceptions and create an end-of-stream iterator, to avoid weakening the exception spec of an existing function?]

    -3- Effects: Initializes sbuf_ with s. If s is not null, initializes c_ with s->sgetc(). Sets sbuf_ to null if sgetc exits via an exception, or if traits_type::eq_int_type(c_, traits_type::eof()) is true.

    istreambuf_iterator(const proxy& p) noexcept;
    

    -4- Effects: Initializes sbuf_ with p.sbuf_. If p.sbuf_ is not null, initializes c_ with p.keep_.

  3. Change 25.6.4.4 [istreambuf.iterator.ops] as indicated:

    charT operator*() const;
    

    -?- Preconditions: sbuf_ is not null.

    -1- Returns: The character obtained via the streambuf member sbuf_->sgetc(). traits_type::to_char_type(c_).

    -?- Throws: Nothing.

    istreambuf_iterator& operator++();
    

    -?- Preconditions: sbuf_ is not null.

    -2- Effects: As if by sbuf_->sbumpc(). Performs c_ = sbuf_->snextc(), then sets sbuf_ to null if traits_type::eq_int_type(c_, traits_type::eof()) is true.

    -3- Returns: *this.

    proxy operator++(int);
    

    -4- Returns: proxy(sbuf_->sbumpc(), sbuf_).
    Effects: Equivalent to:

    proxy p(**this, sbuf_);
    ++*this;
    return p;
    

    bool equal(const istreambuf_iterator& b) const;
    

    -5- Returns: bool(sbuf_) == bool(b.sbuf_).

    [Note: This is true if and only if both iterators are at end-of-stream, or neither is at end-of-stream, regardless of what streambuf object they use. end note]

    template<class charT, class traits>
      bool operator==(const istreambuf_iterator<charT, traits>& a,
                      const istreambuf_iterator<charT, traits>& b);
    

    -6- Returns: a.equal(b).

    bool equal(const istreambuf_iterator& i, default_sentinel_t s) const;
    

    -7- Returns: i.equal(s) i.sbuf_ == nullptr.