diff mbox series

Fix non-standard behaviour of std::istream_iterator

Message ID 20190619230051.GA21222@redhat.com
State New
Headers show
Series Fix non-standard behaviour of std::istream_iterator | expand

Commit Message

Jonathan Wakely June 19, 2019, 11 p.m. UTC
The current implementation of istream_iterator allows the iterator to be
reused after reaching end-of-stream, so that subsequent reads from the
stream can succeed (e.g. if the stream state has been cleared and stream
position changed from EOF). The P0738R2 paper clarified that the
expected behaviour is to set the stream pointer to null after reaching
end-of-stream, preventing further reads.

This implements that requirement, and adds the new default constructor
to std::ostream_iterator.

	* include/bits/stream_iterator.h (istream_iterator::_M_equal()): Make
	private.
	(istream_iterator::_M_read()): Do not check stream state before
	attempting extraction. Set stream pointer to null when extraction
	fails (P0738R2).
	(operator==(const istream_iterator&, const istream_iterator&)): Change
	to be a hidden friend of istream_iterator.
	(operator!=(const istream_iterator&, const istream_iterator&)):
	Likewise.
	(ostream_iterator::ostream_iterator()): Add default constructor.
	(ostream_iterator::ostream_iterator(ostream_type*, const C*)): Use
	addressof.
	* testsuite/24_iterators/istream_iterator/1.cc: New test.
	* testsuite/24_iterators/ostream_iterator/1.cc: New test.
	* testsuite/24_iterators/ostream_iterator/70766.cc: Also check
	constructor taking a string.
	* testsuite/24_iterators/ostream_iterator/requirements/constexpr.cc:
	New test.

Tested x86_64-linux, committed to trunk.
commit 4af63f8369114e24f1ddab531f06fb3902a56bf4
Author: redi <redi@138bc75d-0d04-0410-961f-82ee72b054a4>
Date:   Wed Jun 19 22:57:10 2019 +0000

    Fix non-standard behaviour of std::istream_iterator
    
    The current implementation of istream_iterator allows the iterator to be
    reused after reaching end-of-stream, so that subsequent reads from the
    stream can succeed (e.g. if the stream state has been cleared and stream
    position changed from EOF). The P0738R2 paper clarified that the
    expected behaviour is to set the stream pointer to null after reaching
    end-of-stream, preventing further reads.
    
    This implements that requirement, and adds the new default constructor
    to std::ostream_iterator.
    
            * include/bits/stream_iterator.h (istream_iterator::_M_equal()): Make
            private.
            (istream_iterator::_M_read()): Do not check stream state before
            attempting extraction. Set stream pointer to null when extraction
            fails (P0738R2).
            (operator==(const istream_iterator&, const istream_iterator&)): Change
            to be a hidden friend of istream_iterator.
            (operator!=(const istream_iterator&, const istream_iterator&)):
            Likewise.
            (ostream_iterator::ostream_iterator()): Add default constructor.
            (ostream_iterator::ostream_iterator(ostream_type*, const C*)): Use
            addressof.
            * testsuite/24_iterators/istream_iterator/1.cc: New test.
            * testsuite/24_iterators/ostream_iterator/1.cc: New test.
            * testsuite/24_iterators/ostream_iterator/70766.cc: Also check
            constructor taking a string.
            * testsuite/24_iterators/ostream_iterator/requirements/constexpr.cc:
            New test.
    
    git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@272491 138bc75d-0d04-0410-961f-82ee72b054a4
diff mbox series

Patch

diff --git a/libstdc++-v3/include/bits/stream_iterator.h b/libstdc++-v3/include/bits/stream_iterator.h
index bc3353ab10f..247ad5376dd 100644
--- a/libstdc++-v3/include/bits/stream_iterator.h
+++ b/libstdc++-v3/include/bits/stream_iterator.h
@@ -57,6 +57,9 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
     private:
       istream_type*	_M_stream;
       _Tp		_M_value;
+      // This bool becomes false at end-of-stream. It should be sufficient to
+      // check _M_stream != nullptr instead, but historically we did not set
+      // _M_stream to null when reaching the end, so we need to keep this flag.
       bool		_M_ok;
 
     public:
@@ -66,7 +69,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       ///  Construct start of input stream iterator.
       istream_iterator(istream_type& __s)
-      : _M_stream(std::__addressof(__s))
+      : _M_stream(std::__addressof(__s)), _M_ok(true)
       { _M_read(); }
 
       istream_iterator(const istream_iterator& __obj)
@@ -76,6 +79,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
 #if __cplusplus >= 201103L
       istream_iterator& operator=(const istream_iterator&) = default;
+      ~istream_iterator() = default;
 #endif
 
       const _Tp&
@@ -111,37 +115,38 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	return __tmp;
       }
 
+    private:
       bool
       _M_equal(const istream_iterator& __x) const
-      { return (_M_ok == __x._M_ok) && (!_M_ok || _M_stream == __x._M_stream); }
+      {
+	// Ideally this would just return _M_stream == __x._M_stream,
+	// but code compiled with old versions never sets _M_stream to null.
+	return (_M_ok == __x._M_ok) && (!_M_ok || _M_stream == __x._M_stream);
+      }
 
-    private:
       void
       _M_read()
       {
-	_M_ok = (_M_stream && *_M_stream) ? true : false;
-	if (_M_ok)
-	  {
-	    *_M_stream >> _M_value;
-	    _M_ok = *_M_stream ? true : false;
-	  }
+        if (_M_stream && !(*_M_stream >> _M_value))
+          {
+            _M_stream = 0;
+            _M_ok = false;
+          }
       }
+
+      /// Return true if the iterators refer to the same stream,
+      /// or are both at end-of-stream.
+      friend bool
+      operator==(const istream_iterator& __x, const istream_iterator& __y)
+      { return __x._M_equal(__y); }
+
+      /// Return true if the iterators refer to different streams,
+      /// or if one is at end-of-stream and the other is not.
+      friend bool
+      operator!=(const istream_iterator& __x, const istream_iterator& __y)
+      { return !__x._M_equal(__y); }
     };
 
-  ///  Return true if x and y are both end or not end, or x and y are the same.
-  template<typename _Tp, typename _CharT, typename _Traits, typename _Dist>
-    inline bool
-    operator==(const istream_iterator<_Tp, _CharT, _Traits, _Dist>& __x,
-	       const istream_iterator<_Tp, _CharT, _Traits, _Dist>& __y)
-    { return __x._M_equal(__y); }
-
-  ///  Return false if x and y are both end or not end, or x and y are the same.
-  template <class _Tp, class _CharT, class _Traits, class _Dist>
-    inline bool
-    operator!=(const istream_iterator<_Tp, _CharT, _Traits, _Dist>& __x,
-	       const istream_iterator<_Tp, _CharT, _Traits, _Dist>& __y)
-    { return !__x._M_equal(__y); }
-
   /**
    *  @brief  Provides output iterator semantics for streams.
    *
@@ -171,6 +176,11 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       const _CharT*	_M_string;
 
     public:
+#if __cplusplus > 201703L
+      constexpr ostream_iterator() noexcept
+      : _M_stream(nullptr), _M_string(nullptr) { }
+#endif
+
       /// Construct from an ostream.
       ostream_iterator(ostream_type& __s)
       : _M_stream(std::__addressof(__s)), _M_string(0) {}
@@ -186,7 +196,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
        *  @param  __c  CharT delimiter string to insert.
       */
       ostream_iterator(ostream_type& __s, const _CharT* __c)
-      : _M_stream(&__s), _M_string(__c)  { }
+      : _M_stream(std::__addressof(__s)), _M_string(__c)  { }
 
       /// Copy constructor.
       ostream_iterator(const ostream_iterator& __obj)
@@ -205,7 +215,8 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 				_M_message(__gnu_debug::__msg_output_ostream)
 				._M_iterator(*this));
 	*_M_stream << __value;
-	if (_M_string) *_M_stream << _M_string;
+	if (_M_string)
+          *_M_stream << _M_string;
 	return *this;
       }
 
diff --git a/libstdc++-v3/testsuite/24_iterators/istream_iterator/1.cc b/libstdc++-v3/testsuite/24_iterators/istream_iterator/1.cc
new file mode 100644
index 00000000000..78b5f7df0c5
--- /dev/null
+++ b/libstdc++-v3/testsuite/24_iterators/istream_iterator/1.cc
@@ -0,0 +1,77 @@ 
+// Copyright (C) 2019 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// C++98 24.5.1 Template class istream_iterator
+
+#include <iterator>
+#include <sstream>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+
+void test01()
+{
+  using namespace std;
+
+  const int N = 6;
+  int arr[N]  = { };
+  using __gnu_test::test_container;
+  using __gnu_test::output_iterator_wrapper;
+  test_container<int, output_iterator_wrapper> con(arr, arr+N);
+  output_iterator_wrapper<int> out = con.begin();
+
+  istringstream ss("1 2 3 4 5");
+  std::istream_iterator<int> iter(ss), end;
+  while (iter != end)
+    *out++ = *iter++;
+  VERIFY( iter == end );
+  for (int i = 0; i < N; ++i)
+    VERIFY( arr[i] == (i + 1) % N );
+
+  std::istream_iterator<int> iter2(ss);
+  VERIFY( iter2 == end );
+
+  ss.clear();
+  ss.str("-1 -2 -3");
+  VERIFY( iter == end );
+
+#ifndef _GLIBCXX_DEBUG
+  // This is undefined, so aborts under debug mode.
+  // Without debug mode, it should not extract anything from the stream,
+  // and the iterator should remain at end-of-stream.
+  ++iter;
+  VERIFY( iter == end );
+#endif
+
+  std::istream_iterator<int> iter3(ss);
+  VERIFY( iter3 != end );
+  VERIFY( iter3 != iter );
+  VERIFY( *iter3 == -1 );
+  ++iter3;
+  VERIFY( *iter3 == -2 );
+
+  iter2 = iter3;
+  VERIFY( *iter2 == -2 );
+  ++iter2;
+  VERIFY( *iter2 == -3 );
+  ++iter2;
+  VERIFY( iter2 == end );
+}
+
+int main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/24_iterators/ostream_iterator/1.cc b/libstdc++-v3/testsuite/24_iterators/ostream_iterator/1.cc
new file mode 100644
index 00000000000..6d12bfbef4e
--- /dev/null
+++ b/libstdc++-v3/testsuite/24_iterators/ostream_iterator/1.cc
@@ -0,0 +1,48 @@ 
+// Copyright (C) 2016-2019 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-do compile { target c++11 } }
+
+#include <iterator>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  std::ostringstream ss;
+  std::ostream_iterator<int> iter(ss);
+  for (int i = 0; i < 5; ++i)
+    *iter++ = i;
+  VERIFY( ss.str() == "01234" );
+}
+
+void
+test02()
+{
+  std::ostringstream ss;
+  std::ostream_iterator<int> iter(ss, " - ");
+  for (int i = 0; i < 5; ++i)
+    *iter++ = i;
+  VERIFY( ss.str() == "0 - 1 - 2 - 3 - 4 - " );
+}
+
+int main()
+{
+  test01();
+  test02();
+}
diff --git a/libstdc++-v3/testsuite/24_iterators/ostream_iterator/70766.cc b/libstdc++-v3/testsuite/24_iterators/ostream_iterator/70766.cc
index cc0aa21f50e..c2e069e8e22 100644
--- a/libstdc++-v3/testsuite/24_iterators/ostream_iterator/70766.cc
+++ b/libstdc++-v3/testsuite/24_iterators/ostream_iterator/70766.cc
@@ -33,4 +33,5 @@  test01()
 {
   std::basic_ostream<char, adl::traits> os(nullptr);
   std::ostream_iterator<int, char, adl::traits> oi(os);
+  std::ostream_iterator<int, char, adl::traits> oi2(os, "");
 }
diff --git a/libstdc++-v3/testsuite/24_iterators/ostream_iterator/requirements/constexpr.cc b/libstdc++-v3/testsuite/24_iterators/ostream_iterator/requirements/constexpr.cc
new file mode 100644
index 00000000000..0fcc8f752cc
--- /dev/null
+++ b/libstdc++-v3/testsuite/24_iterators/ostream_iterator/requirements/constexpr.cc
@@ -0,0 +1,24 @@ 
+// Copyright (C) 2019 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++2a" }
+// { dg-do compile { target c++2a } }
+
+#include <iterator>
+
+constexpr std::ostream_iterator<int> iter1;
+constexpr std::ostream_iterator<int> iter2{};