This patch changes the resturn type of istreambuf_iterator::operator++(int),
to dedicated __proxy type. This better reflect the standard, and allows
better optimization as _M_c is always set to EoF on normal path.

As _M_c may still have non-EoF value for the iterator returned by
post-increment, was produced and returned ifrom function defined in TU compiled
with any previous GCC version. Because of that, we also provide a implicit
conversion from __proxy to istream_iterator, that provides source compatibility.
Such patchs are considered unlikely, and thus marked as such.

We preserve extension allowing past-the-end iterator to be dereferenced,
and simplify the _M_at_eof value, by checking for _M_buf first.

libstdc++-v3/ChangeLog:

        * include/bits/streambuf_iterator.h (istreambuf_iterator::__proxy):
        Define.
        (istreambuf_iterator::operator*): Inline _M_get, make path using
        _M_c unlikely.
        (istreambuf_iterator::operator++(int)): Return __proxy, and store
        updated _M_sbuf pointer (null-for eof).
        (istreambuf_iterator::_M_get): Remove.
        (istreambuf_iterator::_M_at_eof): Inline _M_get and ignore _M_c.
        * testsuite/24_iterators/istreambuf_iterator/sentinel.cc:
        * testsuite/24_iterators/istreambuf_iterator/postinc.cc: New test.

Co-authored by: Jonathan Wakely <[email protected]>
Signed-off-by: Tomasz Kamiński <[email protected]>
---
Updated your implementation replacing __proxy for the return of
post-increment. This eliminate stores to _M_c during iterator.
The only case when it is used is when iterator is covnerted from
__proxy (or was returned from older TU), and we handle that in
dereference.

This provided further speedup (_sent test uses default_sentinel),
when after the change I see for same test:
-------------------------------------------------------------------
Benchmark                         Time             CPU   Iterations
-------------------------------------------------------------------
bench_empty                    1.77 ns         1.76 ns    396575121
bench_stream_create            10.8 ns         10.7 ns     65465918
bench_preinc                    329 ns          328 ns      2114616
bench_postinc                   338 ns          337 ns      2076170
bench_deref_postinc             306 ns          305 ns      2301973
bench_preinc_sent               351 ns          349 ns      1997486
bench_postinc_sent              332 ns          331 ns      2118844
bench_deref_postinc_sent        308 ns          307 ns      2284426

Compared to results before:
-------------------------------------------------------------------
Benchmark                         Time             CPU   Iterations
-------------------------------------------------------------------
bench_empty                    1.79 ns         1.79 ns    385470513
bench_stream_create            10.8 ns         10.7 ns     65436512
bench_preinc                    445 ns          444 ns      1557361
bench_postinc                   444 ns          442 ns      1588147
bench_deref_postinc             310 ns          309 ns      2261358
bench_preinc_sent               448 ns          447 ns      1568568
bench_postinc_sent              452 ns          450 ns      1548360
bench_deref_postinc_sent        311 ns          310 ns      2259266

Testing on x86_64-linux. Is this something we want to land
(technically breaking change)?

 .../include/bits/streambuf_iterator.h         | 46 +++++++++++++------
 .../istreambuf_iterator/postinc.cc            | 40 ++++++++++++++++
 .../istreambuf_iterator/sentinel.cc           | 20 ++++++++
 3 files changed, 91 insertions(+), 15 deletions(-)
 create mode 100644 
libstdc++-v3/testsuite/24_iterators/istreambuf_iterator/postinc.cc

diff --git a/libstdc++-v3/include/bits/streambuf_iterator.h 
b/libstdc++-v3/include/bits/streambuf_iterator.h
index 095928ca4d8..184bc69e0c5 100644
--- a/libstdc++-v3/include/bits/streambuf_iterator.h
+++ b/libstdc++-v3/include/bits/streambuf_iterator.h
@@ -115,6 +115,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       streambuf_type* _M_sbuf;
       int_type        _M_c;
 
+      class __proxy
+      {
+       streambuf_type* _M_sb;
+       char_type _M_keep;
+
+       explicit
+       __proxy(streambuf_type* __sb, char_type __c) _GLIBCXX_NOEXCEPT
+       : _M_sb(__sb), _M_keep(__c) { }
+
+       public:
+       char_type operator*() const _GLIBCXX_NOEXCEPT
+       { return _M_keep; }
+
+      friend class istreambuf_iterator;
+      };
+
     public:
       ///  Construct end of input stream iterator.
       _GLIBCXX_CONSTEXPR istreambuf_iterator() _GLIBCXX_USE_NOEXCEPT
@@ -139,6 +155,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       istreambuf_iterator(streambuf_type* __s) _GLIBCXX_USE_NOEXCEPT
       : _M_sbuf(__s), _M_c(traits_type::eof()) { }
 
+      istreambuf_iterator(__proxy __p) _GLIBCXX_USE_NOEXCEPT
+      : _M_sbuf(__p._M_sb), _M_c(__p._M_keep) { }
+
 #if __cplusplus >= 201103L
       istreambuf_iterator&
       operator=(const istreambuf_iterator&) noexcept = default;
@@ -151,7 +170,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       char_type
       operator*() const
       {
-       int_type __c = _M_get();
+       int_type __c = traits_type::eof();
+       if (!_S_is_eof(_M_c)) [[unlikely]]
+         return traits_type::to_char_type(_M_c);
+       if (_M_sbuf) [[likely]]
+         __c = _M_sbuf->sgetc();
 
 #ifdef _GLIBCXX_DEBUG_PEDANTIC
        // Dereferencing a past-the-end istreambuf_iterator is a
@@ -178,16 +201,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        return *this;
       }
 
-      /// Advance the iterator.  Calls streambuf.sbumpc().
-      istreambuf_iterator
+      /// Advance the iterator.
+      __attribute__((__always_inline__))
+      __proxy
       operator++(int)
       {
-       istreambuf_iterator __old = *this;
-       __old._M_c = _M_sbuf->sgetc();
+       int_type __c = _M_sbuf->sgetc();
        ++*this;
-       return __old;
+       return __proxy(_M_sbuf, __c);
       }
 
+
       // _GLIBCXX_RESOLVE_LIB_DEFECTS
       // 110 istreambuf_iterator::equal not const
       // NB: there is also number 111 (NAD) relevant to this function.
@@ -198,18 +222,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       { return _M_at_eof() == __b._M_at_eof(); }
 
     private:
-      int_type
-      _M_get() const
-      {
-       int_type __ret = _M_c;
-       if (_M_sbuf && _S_is_eof(__ret))
-         __ret = _M_sbuf->sgetc();
-       return __ret;
-      }
 
       bool
       _M_at_eof() const
-      { return _S_is_eof(_M_get()); }
+      { return !_M_sbuf || _S_is_eof(_M_sbuf->sgetc()); }
 
       static bool
       _S_is_eof(int_type __c)
diff --git a/libstdc++-v3/testsuite/24_iterators/istreambuf_iterator/postinc.cc 
b/libstdc++-v3/testsuite/24_iterators/istreambuf_iterator/postinc.cc
new file mode 100644
index 00000000000..0aab7266349
--- /dev/null
+++ b/libstdc++-v3/testsuite/24_iterators/istreambuf_iterator/postinc.cc
@@ -0,0 +1,40 @@
+// { dg-do run { target c++11 } }
+#include <iterator>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+template<typename T, typename = void>
+struct can_increment
+: std::false_type { };
+
+template<typename T>
+struct can_increment<T, decltype(++std::declval<T&>(), void())>
+: std::true_type
+{ };
+
+void
+test_postinc()
+{
+  using iter_type = std::istreambuf_iterator<char>;
+
+  std::istringstream in("str");
+  iter_type iter(in.rdbuf());
+  static_assert( can_increment<decltype(iter)>::value, "sanity check" );
+  auto&& post = iter++;
+  static_assert( ! can_increment<decltype(post)>::value,
+                "cannot increment result of post-increment" );
+  static_assert( std::is_convertible<decltype(post), iter_type>::value,
+                "can convert result of post-increment back to iterator" );
+  VERIFY( *post == 's' );
+  VERIFY( *iter++ == 't' );
+  std::istreambuf_iterator<char> copy = iter++;
+  VERIFY( *copy == 'r' );
+  VERIFY( iter == iter_type() );
+  VERIFY( copy == iter_type() );
+}
+
+int main()
+{
+  test_postinc();
+}
+
diff --git 
a/libstdc++-v3/testsuite/24_iterators/istreambuf_iterator/sentinel.cc 
b/libstdc++-v3/testsuite/24_iterators/istreambuf_iterator/sentinel.cc
index 46ccad26c9b..ab8c529b258 100644
--- a/libstdc++-v3/testsuite/24_iterators/istreambuf_iterator/sentinel.cc
+++ b/libstdc++-v3/testsuite/24_iterators/istreambuf_iterator/sentinel.cc
@@ -45,8 +45,28 @@ test02()
   VERIFY( std::default_sentinel == iter );
 }
 
+
+void
+test03()
+{
+  std::istringstream in("abc");
+  std::istreambuf_iterator<char> iter(in);
+  VERIFY( iter != std::default_sentinel );
+  VERIFY( std::default_sentinel != iter );
+
+  auto iter2 = std::next(iter, 3);
+  VERIFY( iter2 == std::default_sentinel );
+  VERIFY( std::default_sentinel == iter2 );
+
+  // We should not use the original object after we've incremented a copy,
+  // so the standard does not require this to work.
+  VERIFY( iter == std::default_sentinel );
+  VERIFY( std::default_sentinel == iter );
+}
+
 int main()
 {
   test01();
   test02();
+  test03();
 }
-- 
2.54.0

Reply via email to