user32849
user32849

Reputation: 649

Trying to understand cin behavior

I am writing software that takes a (huge) input stream from stdin and reads it into a vector of floats. I want to capture the case that the stream contains characters like commas, and either fail to accept it, or simply ignore everything that cannot be parsed as float (whichever is easier to implement, I have no preference). I have noticed the following behavior: when I call

echo "1.4, -0.7 890 23e-3" | ./cintest

this version

#include <iostream>
using std::endl;
using std::cin;
using std::cout;

int main ( int argc, const char* argv[] ){
    float val;
    while (cin >> val) {
        cout << val << endl;
    }
    return 0;
}

prints

1.4

while this version

#include <iostream>
using std::endl;
using std::cin;
using std::cout;

int main ( int argc, const char* argv[] ){
    float val;
    while (cin) {
        cin >> val;
        cout << val << endl;
    }
    return 0;
}

prints

1.4
0

Without the comma, the first one prints

1.4
-0.7
890
0.023

while the second one prints

1.4
-0.7
890
0.023
0.023

Could somebody please explain what is going on here?

Upvotes: 2

Views: 225

Answers (4)

Useless
Useless

Reputation: 67872

The first version of your code

    while (cin >> val) {

tries to parse a float, and then checks whether the stream state is good. (Specifically, it calls operator>> to do the extraction, which will set failbit on error, and then uses the bool conversion to test failbit).

So, if the stream state is bad (because it couldn't convert , to a float), the loop body is not entered. Hence it terminates on the first failed conversion.

The second version

    while (cin) {
    cin >> val;

checks if the stream state is good (which just tells you the previous conversion succeeded), then tries parsing a float, and then assumes this succeeded without checking. It ought to check the stream state after conversion before using the float value, which in this case is left over from the previous iteration.

In a correct implementation, when conversion fails, you should check whether fail() is true but eof() is false (ie, the conversion failed for some reason other than end-of-file). In this case, use ignore() to discard input - you could either require whitespace (and ignore until the next space), or just ignore one character and try again.

Note that the ignore documentation linked above includes sample code with correct error handling. If we choose to skip a single character on failed conversions, your code would become:

for(;;) {
    float val;
    std::cin >> val;

    if (std::cin.eof() || std::cin.bad()) {
        break;
    } else if (std::cin.fail()) {
        std::cin.clear(); // unset failbit
        std::cin.ignore(1); // skip next char
    } else {
        std::cout << val << '\n';
    }
}

Upvotes: 3

Loki Astari
Loki Astari

Reputation: 264749

It's because in the second one you have a bug.

You should always check to see if the formatted input operator>> actually worked.

So this code:

    cin >> val;
    cout << val << endl;

Should be written as:

    if (cin >> val) {
        cout << val << endl;
    }

If the operator>> fails. Then it will set one of the fail bits on the stream and not put any value into val. So there is no need to print val because nothing was put into it.

This is why your second version prints garbage when it has no data left to read. The read fails and then you print out a value. Then you try and re-start the loop (which fails).

The first one works correctly.

while (cin >> val) {
    cout << val << endl;
}

Because you read a value then check to see if the read works before entering the loop.

Upvotes: 1

Supun De Silva
Supun De Silva

Reputation: 1487

std::cin is an instantiation of std::istream

when there is a comma or any invalid data type at any point the operator >> fails. hence your code prints the last known value of 'val'.

Linked here is a reference for 'std::istream >>'

http://www.cplusplus.com/reference/istream/istream/operator%3E%3E/

Upvotes: 0

hlt
hlt

Reputation: 6317

Your results have to do with when the >> fails.

In both versions, you read your values and reach the comma (or EOF). The read obviously fails, because , and EOF are not valid integers that >> can parse. So >>'s return value (the stream itself) converts to false, and you exit the loop in the first version (this is how it should work).

In the second version (which isn't how you should usually do it), however, you are still printing out whatever value ends up in val. In C++ before C++11, val remains the same; since C++11 >> writes 0 on failure.

TL;DR: Your second version stops the loop to late, and writes one pass of garbage.

Upvotes: 2

Related Questions