10

Should extracting from a stream using the std::ws manipulator ever raise the fail bit? In the following code, a Clang-compiled (within Xcode 4.5.1) program fails the final assertion. Evidently s >> std::ws at EOF causes a fail. Yet GCC 4.7.2 passes the assertion. Which is correct?

#include <iostream>
#include <sstream>
#include <cassert>

int main(int argc, const char * argv[])
{
    {
        // Read string with trailing ws.
        std::istringstream s( "test   " );
        std::string test;

        s >> std::ws;
        assert( !s.fail() );    // No ws to skip, but no failure.

        s >> test;
        assert( test == "test" );
        assert( !s.fail() );

        s >> std::ws;
        assert( !s.fail() );    // No prob skipping trailing ws.
    }
    {
        // Retry with no trailing ws.
        std::istringstream s( "test" );
        std::string test;

        s >> std::ws;
        assert( !s.fail() );    // No ws to skip, but no failure.

        s >> test;
        assert( test == "test" );
        assert( !s.fail() );

        s >> std::ws;
        assert( !s.fail() );    // CLANG: Skipping absent ws at eof raises failbit.
    }

    return 0;
}
OldPeculier
  • 11,049
  • 13
  • 50
  • 76

2 Answers2

4

I believe that libc++ is implementing the standard correctly.

[ I'm quoting from N3290, which is the draft C++11 standard. C++14 does not change this. ]

ws is described in [istream.manip], which states:

Effects: Behaves as an unformatted input function (as described in 27.7.2.3, paragraph 1), except that it does not count the number of characters extracted and does not affect the value returned by subsequent calls to is.gcount(). After constructing a sentry object extracts characters as long as the next available character c is whitespace or until there are no more characters in the sequence. Whitespace characters are distinguished with the same criterion as used by sentry::sentry (27.7.2.1.3). If ws stops extracting characters because there are no more available it sets eofbit, but not fail bit.

The key phrase here for determining the correct behavior is "after constructing a sentry object".

Sentry objects are described in [istream::sentry], and the text there begins...

1 The class sentry defines a class that is responsible for doing exception safe prefix and suffix operations.

explicit sentry(basic_istream& is, bool noskipws = false);

2 Effects: If is.good() is false, calls is.setstate(failbit). Otherwise, prepares for formatted or > unformatted input. ...and so on...

That's the only sentry constructor that is available, so that's the one that libc++ uses.

So the fail bit gets set before extracts any characters, so the text at the end of the paragraph does not apply. if the stream contained " " (i.e, a single space at the end), then calling std::ws does not set the fail bit, just eof (which is what the OP expected to happen).

Marshall Clow
  • 15,972
  • 2
  • 29
  • 45
  • That seems like a fair reading of the standard to me, but since it leads to a less useful version `std::ws`, I think we should consider GCC's interpretation as the right one. The problem with Clang's version is that it cannot be used to eat optional white space at end of input. – Kristian Spangsege Dec 26 '16 at 02:29
  • If we've got divergence between implementations, then I think that the C++ standards committee should decide what behavior they meant to specify, and re-word that section of the standard so that it is clear :-) – Marshall Clow Dec 27 '16 at 07:42
  • 1
    Directions for creating an LWG issue: http://cplusplus.github.io/LWG/lwg-active.html#submit_issue – Howard Hinnant Dec 27 '16 at 18:19
  • @HowardHinnant: Does Marshall really need the link to know how to create an LWG issue? ;-) – Jerry Coffin Dec 27 '16 at 19:53
  • No, I do not :-) but other people might. – Marshall Clow Dec 28 '16 at 21:04
3

C++11, §27.7.2.4/1:

If ws stops extracting characters because there are no more available it sets eofbit, but not failbit.

So, the ws manipulator doesn't set failbit directly. However, as Marshall Clow points out in his answer, it doesn't have to--it is required to create a sentry object, and the sentry object is required to set the failbit if !stream.good().

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111