9

While libstdc++ does not, libc++ does follow the standard which states that passing ios_base::failbit to basic_istream::exceptions has no effect on formatted input. For example this code:

istringstream is{"ASD"};    
double foo;

is.exceptions(istream::failbit);

try {
    is >> foo;
    cout << foo << endl;
} catch(ios_base::failure& fail) {
    cout << "ouch\n";
}

Would result in:

My reading of LWG2349 is that it would cause basic_istream to not throw on any formatted input.

For example LWG2349 proposes a change to the standard's 27.7.2.3 [istream]/1 which was cited with reference to the invalidation of a bug that would have made libc++ behave like libstdc++. The change is in bold and strike through below:

If an exception , other than the ones thrown from clear(), if any, is thrown during input then ios::badbit is turned on in *this’s error state. (Exceptions thrown from basic_ios<>::clear() are not caught or rethrown.) If (exceptions()&badbit) != 0 then the exception is rethrown.

I understand that basic_istream::clear is what throws in reaction to bad formatted input so am I misreading LWG2349 or is it in fact going to stop basic_istream from throwing any errors?

Community
  • 1
  • 1
Jonathan Mee
  • 35,107
  • 16
  • 95
  • 241

1 Answers1

9

The point of the language excluding exceptions "thrown from clear()" is to ensure that if clear() throws, because an input function has called clear(failbit) and (exceptions() & failbit) != 0, then badbit is not set as a result. clear() will continue to throw in that case, it just will not set badbit.

As described in the commentary to LWG2349, the intention is that badbit is set when an exception is thrown from user code:

PJ and Matt both agree that the intention (of badbit + rethrow) is "to signify an exception arising in user code, not the iostreams package".

Now, when can an exception be thrown by "user code" but within the iostreams machinery? One example would be by the locale getters:

struct my_get : std::num_get<char> {
    using iter_type = std::istreambuf_iterator<char>;
    iter_type do_get(iter_type, iter_type, std::ios_base&, std::ios_base::iostate&, bool&) const override {
        throw std::logic_error{"my_get::do_get"};
    }
};
int main() {
    std::istringstream iss;
    iss.imbue({std::locale{}, new my_get});
    iss.exceptions(std::ios_base::failbit | std::ios_base::badbit);
    try {
        bool b;
        iss >> b;
    } catch (std::exception& ex) {
        std::cout << ex.what() << '\n';
    }
    std::cout
        << ((iss.rdstate() & std::ios_base::eofbit) ? "eof " : "")
        << ((iss.rdstate() & std::ios_base::failbit) ? "fail " : "")
        << ((iss.rdstate() & std::ios_base::badbit) ? "bad " : "")
        << '\n';
}

At present, gcc outputs:

eof fail

clang outputs:

eof fail

After LWG2349, the correct behavior is to set badbit and rethrow the exception:

my_get::do_get
eof bad
ecatmur
  • 137,771
  • 23
  • 263
  • 343
  • Your 2nd sentence says: "`clear()` will continue to throw in that case, it just will not set `badbit`." And that throw would still *not* be rethrown, if the `exceptions() & ios_base::badbit == 0`, right? So this won't do anything to align the question's example behaviors of libc++ and libstdc++? – Jonathan Mee Jan 29 '16 at 17:13
  • 1
    @JonathanMee the intention is that the throw from `clear()` is not caught (or, if it is caught, it is invisibly rethrown without setting `badbit`); the catch and rethrow only applies to user-thrown exceptions. So in the question's example, the correct output would be "ouch". – ecatmur Jan 29 '16 at 19:03
  • So that's what T.C. says here: http://stackoverflow.com/questions/35019687/stdistream-operator-exception-reset-not-thrown/35020134?noredirect=1#comment57830044_35020134 but I don't get it. The qualification for when the error is rethrown hasn't changed. Why would this ever rethrow, especially if it wasn't rethrowing before? – Jonathan Mee Jan 29 '16 at 19:07
  • 2
    @JonathanMee it helps to be aware of e.g. 27.7.2.1 [istream]/4: "If one of these called functions throws an exception, then unless explicitly noted otherwise, the input function sets badbit in error state. If badbit is on in exceptions(), the input function rethrows the exception without completing its actions, otherwise it does not throw anything and proceeds as if the called function had returned a failure indication." The purpose of LWG2349 is to ensure that user-thrown exceptions from other user code (not just `sbumpc` and `sgetc`) behave similarly. – ecatmur Jan 29 '16 at 21:06
  • Although I haven't been able to get @T.C. to respond, what I'm truly interested in is understanding [why T.C. says this is related to how libc++ handles the `exceptions` mask](http://stackoverflow.com/questions/35019687/stdistream-operator-exception-reset-not-thrown?lq=1#comment57830044_35020134) You allude to it in your response, but as with my example I want to know if this will change how libc++ will behave if only `ios_base::failbit` is passed to `exceptions` could you elaborate a bit on that in your answer? – Jonathan Mee Feb 02 '16 at 13:39
  • @JonathanMee libc++ should throw if `failbit` is set in `exceptions` and `clear` sets `failbit`; but LWG2349 doesn't make any difference to this. The relevant passage is [string.io]/9 "If the function extracts no characters, it calls `is.setstate(ios_base::failbit)` which may throw `ios_base::failure`". Unfortunately it looks like @Howard Hinnant has a misconception regarding the purpose of `badbit` in `exceptions`. I haven't yet found a library that gets everything right; see http://coliru.stacked-crooked.com/a/4f82ca590eb41ba5 – ecatmur Feb 08 '16 at 11:42
  • Wow, that's some great testing. But I'm not certain that I agree. Shouldn't the rethrow only happen "If (exceptions()&badbit) != 0 then the exception is rethrown." That's what @HowardHinnant is using to interpret I believe. If that's not the correct interpretation then what's the meaning of that sentence? – Jonathan Mee Feb 08 '16 at 16:57
  • @JonathanMee that sentence applies to the last example, where `mybuf` throws an exception on `underflow`; that exception is caught, setting `badbit` on `rdstate`, and then rethrown if `badbit` is set in `exceptions`. In the other examples the `basic_ios::failure` exception is thrown by `clear` so is not caught in the first place. – ecatmur Feb 08 '16 at 19:08
  • @JonathanMee In general, don't put too much emphasis on the wording for "New" or "Open" issues. Those usually haven't undergone full review at that point and may be incomplet, incorrekt, or both. – T.C. Feb 10 '16 at 09:44
  • @T.C. I'm glad you responded.This question was designed to help clarify in my mind what LWG2349 has to do with a non-`ios_base::badbit` being set on `basic_istream::exceptions` and that bit being set in the `basic_istream` but no exception being thrown. You say [here](http://bit.ly/1SfVI4w) that they are directly related, but me and ecatmur agree throughout this conversation that LWG2349 has nothing to do with this problem. I was actually gonna accept this answer today, but if you could clarify what we're missing that'd be awesome. – Jonathan Mee Feb 10 '16 at 14:12
  • @JonathanMee We all know that [string.io]/9 says it calls `setstate` which eventually throws `ios_base::failure`; the question is whether the general unformatted input requirements require that exception to then be caught, `badbit` to be turned on, and the exception (possibly) rethrown (depending on the exceptions mask). The intention appears to be that anything thrown from `clear()` is not subject to the catch-set `badbit`-rethrow process. LWG2349 is an attempt to make the standardese correctly reflect that intent. – T.C. Feb 10 '16 at 14:31
  • @T.C. LWG2349 would change the test to read: "If an exception, other than the ones thrown from `clear()`, if any, is thrown during input then `ios::badbit` is turned on in `*this`’s error state. If `(exceptions()&badbit) != 0` then the exception is rethrown." You're interpreting that to mean that `clear()` throws are not caught by `basic_istream`? Then wasn't it clearer before this change: "(Exceptions thrown from `basic_ios<>::clear()` are not caught or rethrown.)" – Jonathan Mee Feb 10 '16 at 15:00
  • 1
    @JonathanMee That particular change was apparently intended to be stylistic - the discussion section says that the previous version with the parenthetical "lost some context (the word "rethrown" is not seen before this sentence within this section)". Again, at this point, one should focus on the intent, not the exact proposed wording. – T.C. Feb 10 '16 at 15:03
  • ecatmur, do you agree that what @T.C. is saying? That LWG2349 is about throwing whenever `clear` sets a bit also set in the `exceptions` mask? That is throwing and not catching to rethrow, just throwing it all the way. – Jonathan Mee Feb 18 '16 at 12:57
  • 1
    @JonathanMee yes, definitely. I think a small part of the problem is that throwing the `clear` exception "all the way" is not actually possible - a conformant implementation would have to wrap user operations in a try block, so would have to specifically catch `ios_base::failure` and rethrow it with `throw;`. But that's an implementation detail - it should indeed appear to the user as if the `ios_base::failure` thrown by `clear` is thrown all the way. – ecatmur Feb 18 '16 at 15:40