Reputation: 649
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
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
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
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
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