std::istream operator exception reset / not thrown
Asked Answered
U

2

3

I'm not sure about how to use std::istream::exception according to the standard, to let std::istream::operator>> throw an exception if it can't read the input into a variable, e.g. double. The following code has different behavior with clang/libc++ and gcc/libstdc++:

#include <iostream>
#include <cassert>

int main () {
    double foo,bar;
    std::istream& is = std::cin;

    is.exceptions(std::istream::failbit);
    is >> foo; //throws exception as expected with gcc/libstdc++ with input "ASD"
    std::cout << foo;
    is >> bar;
    std::cout << bar;
    assert(is); //failed with clang/libc++ after input "ASD"

    std::cout << foo << " " << bar << std::endl;

}

Is is.exceptions(std::istream::failbit); right for the purpose to let operator>> throw, according to the C++ standard?

Urceolate answered 26/1, 2016 at 17:11 Comment(4)
@πάνταῥεῖ Sure they use libc++? Reproduced it with Apple LLVM version 7.0.2 (clang-700.1.81) and libc++ 1101 .Urceolate
It reproduces with libc++. Comparing std::lib issues using gcc/libstdc++ vs clang/libstdc++ is almost always pointless, but people still insist on doing it :-\Melodious
Possible duplicate of libc++ std::istringstream doesn't thrown exceptions. Bug?Cankerous
This is LWG issue 2349.Unsatisfactory
C
7

First some background information (each of these is explained below under it's respective title if you would like further elaboration):

  • The standard requires istreams to rethrow only when ios_base::badbit is set in basic_istream::exceptions
  • libstdc++ does not comply with this requirement but libc++ does
  • libc++ invalidates bugs requesting it's mirroring of libstdc++ behavior
  • libc++ proffers ios_base::badbit bit-wise ored with the desired ios_base::iostate as a workaround

Unfortunately this workaround has the side effect of also rethrowing whenever ios_base::badbit is set independent of ios_base::failbit: http://en.cppreference.com/w/cpp/io/ios_base/iostate#The_badbit

If you're looking for a throw to happen only when ios_base::failbit is set and you need this to have the same behavior on libc++ and libstdc++ you'll have to check the ios_base::badbit after each input operation occurring on the istream. That'd need to look something like this:

if((is.rdstate() & ios_base::failbit) != 0) throw ios_base::failure("basic_ios::clear");

As noted by cpplearner you can't even use basic_istream::fail, you have to do a bit-wise test of the istream's rdstate return. But honestly that only adds a bit of complexity.

What could make this a monumental task is the extent to which the istream is used. Wide usage of the istream could be combated by helper functions, but use of istream_iterators or compound overloads of the extraction operator quickly make the manual inspection of this an unreasonable task.

If you find yourself there I would seriously consider the possibility of the is.exceptions(ios_base::failbit | ios_base::badbit) workaround.


The standard requires istreams to rethrow only when ios_base::badbit is set in basic_istream::exceptions

Calling basic_istream::exceptions(istream::failbit) will set a mask which can be retrieved by calling basic_istream::exceptions() which according to 27.5.5.4 [iosstate.flags]/11 of the standard is:

A mask that determines what elements set in rdstate() cause exceptions to be thrown.

This is supported in 27.7.2.2.3 [istream::extractors]/15 for unformated insertion methods:

If it inserted no characters because it caught an exception thrown while extracting characters from *this and failbit is on in exceptions() (27.5.5.4), then the caught exception is rethrown.

However for formatted input this is retrograded in 27.7.2.2.1 [istream.formatted.reqmts]/1; requiring a throw to occur only when a bit-wise and of the mask and ios_base::badbit is non-zero:

If an exception is thrown during input then ios::badbit is turned on in *this’s error state. If (exceptions()&badbit) != 0 then the exception is rethrown.


libstdc++ does not comply with this requirement but libc++ does

The ios_base::failbit should be set on it's respective istream on events such as:

The numeric, pointer, and boolean input overloads of basic_istream::operator>> (technically, the overloads of num_get::get they call), if the input cannot be parsed as a valid value or if the value parsed does not fit in the destination type.

[Source]

If only the ios_base::failbit is set on a basic_istream::exceptions' mask and an event occurs, causing the ios_base::failbit to be set, such as extracting an invalid number as described above:


libc++ invalidates bugs requesting it's mirroring of libstdc++ behavior

There is a now invalidated bug against libc++ for this very issue. Citing 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.


libc++ proffers ios_base::badbit bit-wise ored with the desired ios_base::iostate as a workaround

Our own Howard Hinnant (who also happens to be libc++'s representative who invalidated the linked libc++ bug) suggests in answer to a duplicate of this question (as well as in the libc++ bug) that you use the workaround:

is.exceptions(ios_base::failbit | ios_base::badbit);
Cankerous answered 26/1, 2016 at 17:33 Comment(14)
Try using -stdlib=libc++ otherwise you're just using the same std::lib as GCC uses, which doesn't confirm anything about clang's library.Melodious
@JonathanWakely Thanks, that was a very helpful comment. I mistakenly though that Clang compiled with libc++ by default.Cankerous
fail() returns true if either failbit or badbit is set. To test failbit only, one needs to write is.rdstate() | std::ios_base::failbit.Quesada
@Quesada Thanks for the tip. I've updated my answer. It appears fail is a bit of a misnomer for this function :S To whom it may concern, your bitwise or should have been a bitwise and: is.rdstate() & istream::failbit != 0Cankerous
If you want to make a statement about "any compliant compiler" (or really, library implementation), I'd like to see a quote from the standard, not cppreference, especially when you are contradicting a committee member who did cite the standard.Unsatisfactory
@Unsatisfactory I'd love to, but I don't have a copy of the standard T.T I have however provided the bug report documenting this issue with libc++. Also, while not the standard there is a bit more direct quote at: cplusplus.com/reference/ios/ios/exceptions "The exception mask is an internal value kept by all stream objects specifying for which state flags an exception of member type failure (or some derived type) is thrown when set."Cankerous
"I have however provided the bug report documenting this issue with libc++"...whose (then-)maintainer does not think is a bug, based on his reading of the standard text. Perhaps Howard Hinnant was wrong, but it takes more than isolated quotes from nonauthoritative sources like cppreference or cplusplus.com to prove it. The latest working draft (N4567) is freely available online.Unsatisfactory
@Unsatisfactory Thanks for the heads up. You are right, it is not libstdc++ that's compliant it's libc++ :( I've updated my answer. Thanks for keeping me honest.Cankerous
27.7.2.2.3 [istream::extractors]/15 (please separate paragraph numbers and section numbers; it can be ambiguous otherwise) is about the streambuf extractor, not all unformatted input; the common requirements for unformatted input is in 27.7.2.3 [istream.unformatted]/1. 27.7.2.2.1 [istream.formatted.reqmts] is about formatted input, not unformatted input. Finally, the answer should discuss LWG2349.Unsatisfactory
@Unsatisfactory I don't believe LWG2349 has anything to do with this. In my understanding that's a bug against class provided extraction operators which throw errors, like bitset's extraction operator. If I've misunderstood that and you could correct me I'd love to include it in this answer, although I'll probably need to add a table of contents as well too ;)Cankerous
LWG2349 has everything to do with this. It deals with the question "are formatted/unformatted input functions supposed to swallow exceptions thrown from basic_ios::clear (which is the function that's actually responsible for throwing ios_base::failure based on the mask)?" bitset's >> is just an example of a formatted input function.Unsatisfactory
Let us continue this discussion in chat.Cankerous
@Unsatisfactory I've asked here to get more clarification on LWG2349 if you care to chime in.Cankerous
@Quesada Thank you for that correction. Clearly I didn't test that properly. I can't believe that I cited your answer in mine and didn't upvote you. But since it has come to my attention, here, have an upvote!Cankerous
Q
2

It seems that libc++ follows this requirement:

Each formatted input function begins execution by constructing an object of class sentry with the noskipws (second) argument false. If the sentry object returns true, when converted to a value of type bool, the function endeavors to obtain the requested input. If an exception is thrown during input then ios::badbit is turned on312 in *this’s error state. If (exceptions()&badbit) != 0 then the exception is rethrown. In any case, the formatted input function destroys the sentry object. If no exception has been thrown, it returns *this.

312) This is done without causing an ios::failure to be thrown.

(quoted from N4567 § 27.7.2.2.1 [istream.formatted.reqmts] but the C++ IS contains identical wording. Emphasis mine)

I don't know whether this really implies "if (exceptions()&badbit) == 0 then the input function shall not throw any exception", though.

Quesada answered 26/1, 2016 at 18:10 Comment(2)
This sounds sound finally.Hauberk
It's interesting, I linked as a duplicate, and this is the exact passage that Howard Hinnant uses to defend this bug as invalid. But I don't believe that the bug is invalid. I believe that the solution in my answer: if(is.fail()) throw ios_base::failure("basic_ios::clear"); may be the only way to rectify libc++'s misbehavior with libstdc++'s.Cankerous

© 2022 - 2024 — McMap. All rights reserved.