This is the mail archive of the libstdc++@gcc.gnu.org mailing list for the libstdc++ project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: Make tests less istreambuf_iterator implementation dependent


On 02/10/17 07:43 +0200, François Dumont wrote:
On 28/09/2017 23:56, Jonathan Wakely wrote:
On 28/09/17 21:59 +0200, François Dumont wrote:

The current istreambuf_iterator implementation capture the current streambuf state each time it is tested for eof or evaluated. This is why I considered those tests as fragile.

Yes, and I think that's non-conforming.


Good, then we have something to fix.

As I said in the other thread, I'd really like to see references to
the standard used to justify any changes to our istreambuf_iterator

I think _M_c has been introduced to cover this paragraph of the Standard:

24.6.3.1

"1. Class istreambuf_iterator<charT,traits>::proxy is for exposition only. An implementation is permit- ted to provide equivalent functionality without providing a class with this name. Class istreambuf_- iterator<charT, traits>::proxy provides a temporary placeholder as the return value of the post- increment operator (operator++). It keeps the character pointed to by the previous value of the iterator for
some possible future access to get the character."

This is why it is being set in operator++(int):

    istreambuf_iterator __old = *this;
    __old._M_c = _M_sbuf->sbumpc();

It is also the reason why libstdc++ fails:
http://llvm.org/svn/llvm-project/libcxx/trunk/test/std/iterators/stream.iterators/istreambuf.iterator/istreambuf.iterator.cons/proxy.pass.cpp


It looks like this test is simply not Standard conformant.

I think you're right, because it relies on a property that may not be
true:

 any copies of the previous
 value of r are no longer
 required either to be
 dereferenceable or to be in
 the domain of ==.

I guess at some point _M_c started to be used also to cache the streambuf::sgetc resulting in current situation.

And arguably that is not "equivalent functionality" to returning a
proxy object. Although the standard seems a bit underspecified.

In attached patch I limit usage of _M_c to cover 24.6.3.1.1 point and so made additional changes to 2.cc test case to demonstrate it.

Doesn't the modified test PASS anyway, even without changing how _M_c
is used?

Generally this looks good (I'm not sure if it addresses Petr's
concerns, but I don't think his "stop and go" usage is valid anyway -
it's certainly not portable or guaranteed by the standard).

@@ -103,7 +103,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
      : _M_sbuf(0), _M_c(traits_type::eof()) { }

#if __cplusplus >= 201103L
-      istreambuf_iterator(const istreambuf_iterator&) noexcept = default;
+      istreambuf_iterator(const istreambuf_iterator&) = default;

Please don't remove this.

It's present in the standard, and it ensures we don't accidentally add
a member that makes the default noexcept(false).


      ~istreambuf_iterator() = default;
#endif
@@ -122,28 +122,29 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
      char_type
      operator*() const
      {
+	int_type __c = _M_get();
+
#ifdef _GLIBCXX_DEBUG_PEDANTIC
	// Dereferencing a past-the-end istreambuf_iterator is a
	// libstdc++ extension
-	__glibcxx_requires_cond(!_M_at_eof(),
+	__glibcxx_requires_cond(!_S_at_eof(__c),
				_M_message(__gnu_debug::__msg_deref_istreambuf)
				._M_iterator(*this));
#endif
-	return traits_type::to_char_type(_M_get());
+	return traits_type::to_char_type(__c);
      }

      /// Advance the iterator.  Calls streambuf.sbumpc().
      istreambuf_iterator&
      operator++()
      {
-	__glibcxx_requires_cond(!_M_at_eof(),
+	__glibcxx_requires_cond(_M_sbuf &&
+				(!_S_at_eof(_M_c) || !_S_at_eof(_M_sbuf->sgetc())),
				_M_message(__gnu_debug::__msg_inc_istreambuf)
				._M_iterator(*this));
-	if (_M_sbuf)
-	  {

This check should be unnecessary, because it's undefined to increment
and end-of-stream iterator, but I wonder if anyone is relying on it
being currently a no-op, rather than crashing.

Let's remove the check, and if it causes too many problems we can
restore it as:

 if (__builtin_expect(_M_sbuf != 0, 1))

+
	_M_sbuf->sbumpc();
	_M_c = traits_type::eof();
-	  }
	return *this;
      }

@@ -151,16 +152,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
      istreambuf_iterator
      operator++(int)
      {
-	__glibcxx_requires_cond(!_M_at_eof(),
+	__glibcxx_requires_cond(_M_sbuf &&
+				(!_S_at_eof(_M_c) || !_S_at_eof(_M_sbuf->sgetc())),
				_M_message(__gnu_debug::__msg_inc_istreambuf)
				._M_iterator(*this));

	istreambuf_iterator __old = *this;
-	if (_M_sbuf)
-	  {
	__old._M_c = _M_sbuf->sbumpc();
	_M_c = traits_type::eof();
-	  }
	return __old;
      }

@@ -176,26 +175,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
      int_type
      _M_get() const
      {
-	const int_type __eof = traits_type::eof();
-	int_type __ret = __eof;
-	if (_M_sbuf)
-	  {
-	    if (!traits_type::eq_int_type(_M_c, __eof))
-	      __ret = _M_c;
-	    else if (!traits_type::eq_int_type((__ret = _M_sbuf->sgetc()),
-					       __eof))
-	      _M_c = __ret;

Removing this assignment means that we will call _M_sbuf->sgetc() for
every dereference operation, instead of caching the current value in
_M_c. I think that's probably OK, because it's unlikely that
performance critical code is dereferencing the same iterator
repeatedly without incrementing it.

And getting rid of the mutable member is a Good Thing.

An alternative would be to cache it in operator*() instead of in
_M_get(), which would still give the desired change of not updating it
when _M_get() is used elsewhere. But would still involve setting a
mutable member in a const function.

-	    else
+	int_type __ret = _M_c;
+	if (_M_sbuf && _S_at_eof(__ret) && _S_at_eof(__ret = _M_sbuf->sgetc()))
	  _M_sbuf = 0;

It's unfortunate that we still have this assignment to a mutable
member in a const function, but I think it's necessary to avoid an
end-of-stream iterator becoming valid again.

-	  }
	return __ret;
      }

      bool
      _M_at_eof() const
+      { return _S_at_eof(_M_get()); }
+
+      static bool
+      _S_at_eof(int_type __c)

This should be called _S_is_eof, since it tests if the argument **is**
the EOF character. It doesn't test if the stream is **at** EOF, like
_M_at_eof does.

So with a suitable ChangeLog, the "noexcept" kept on the constructor,
and renaming _S_at_eof to _S_is_eof, this is OK for trunk.




Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]