I upgraded my compiler from gcc-4.4 to gcc-4.8 and one project fails miserably stemming from the following (false) assumptions:

#include <sstream>
#include <assert.h>

int main()
{
    using namespace std;
    istringstream iScan;
    int num;

    //iScan.unsetf(std::ios::skipws);
    iScan.str("5678");
    iScan >> num;
    assert(iScan.tellg() == istringstream::pos_type(4));
    assert(!iScan.fail());
    assert(!iScan.good());
    assert(iScan.eof());
    assert(num == 5678);
    assert(false && "We passed the above assertions.");
    return 0;
}

On gcc-4.4, relevant assertions pass. On gcc-4.8, tellg() returns -1 and and fail() returns !false, apparently since it hit eof.

My target is MinGW 32-bit as shipped with Qt 5.1 (gcc-4.8).

Questions:

  • Is the old behavior really in error, per N3168 or other? (Which other?)
  • Is there a global, reliable, language-independent work-around? (I'm guessing not.)
  • Is there a global, reliable, gcc work-around that spans versions?
  • Even when I do the above unsetf(skipws), it still doesn't work on gcc-4.8. Isn't that incorrect behavior?

Further, various online compilers give different behavior. Is that a function of their lib?

  • compileonline, which claims to be gcc-4.7.2, allows it even though other sources say behavior changed in 4.6.
  • stack-crooked, gcc-4.8, shows the new behavior, and unsetf(skipws) seems to have no effect.
  • codepad allows it. Can't tell version.

Other similar but not duplicate questions:

The body of code, with these assumptions running through it all, is large.

Update: Here's a key part of the answer, which should work across all versions, all compilers:

// istream::tellg() is unreliable at eof(): works w/gcc-4.4, doesn't w/gcc-4.8.
#include <sstream>
#include <assert.h>

using namespace std;
typedef istream::pos_type   pos_type;

pos_type reliable_tellg(istream &iScan)
    {
    bool wasEOF = iScan.eof();
    if (wasEOF)
        iScan.clear(iScan.rdstate() & ~ios::eofbit); // so tellg() works.
    pos_type r = iScan.tellg();
    if (wasEOF)
        iScan.clear(iScan.rdstate() | ios::eofbit); // restore it.
    return r;
    }


int main()
{
    istringstream iScan;
    int num, n2;

    //iScan.unsetf(std::ios::skipws);
    iScan.str("5678");
    assert(!iScan.eof() && !iScan.fail()); // pre-conditions.
    assert(reliable_tellg(iScan) == pos_type(0));

    iScan >> num;
    assert(!iScan.fail());
    assert(reliable_tellg(iScan) == pos_type(4));
    assert(iScan.eof());
    assert(reliable_tellg(iScan) == pos_type(4)); // previous calls don't bungle it.
    assert(num == 5678);

    iScan >> n2; // at eof(), so this should fail.
    assert(iScan.fail());
    assert(reliable_tellg(iScan) == pos_type(-1)); // as expected on fail()
    assert(iScan.eof());

    assert(false && "We passed the above assertions.");
    return 0;
}
有帮助吗?

解决方案

The behavior you seem to expect is probably wrong. Both C++11 and C++03 start the description of tellg with "Behaves as an unformatted input function[...]". An "unformatted input function" starts by constructing a sentry object, and will fail, doing nothing and returning a failure status, if the sentry object converts to false. And the sentry object will convert to false if the eofbit is set.

The standard is slightly less clear about whether reading the number sets the eofbit, but only slightly (with the information spread out over several different sections). Basically, when inputting a numeric value, the stream (actually, the num_get facet) must read one character ahead, in order to know where the number ends. In your case, it will see the end of file when this occurs, and will thus set eofbit. So your first assert will fail with a conforming implementation.

One could very easily consider this a defect in the standard, or unintentional. It's very easy to imagine some implementations doing the sensible thing (which is what you seem to expect), perhaps because the original implementors didn't realize the full implications in the standard (or unconsciously read it as they thought it should read). I would guess that this is the case for g++, and when they realized that their behavior was non-conformant, they fixed it.

As for work-arounds... I'm not sure what the real problem is, that you're trying to work around. But I think that if you clear the error bits before the tellg, it should work. (Of course, then iScan.good() will be true, and iScan.eof() false. But can this really matter?) Just be sure to check that the extraction actually succeeded before you clear the status.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top