Reputation: 411
I am trying to read all the numbers a line of text, one by one.
Here is a minimal reproducible example of my code:
#include <iostream>
#include <sstream>
int main() {
std::string line = "1 2"; // This value represents the next input line
std::stringstream lineStream(line);
for (char nextChar = lineStream.peek(); nextChar != EOF; nextChar = lineStream.peek()) {
int n;
lineStream >> n;
std::cout << "n==" << n << "," << std::flush;
}
return 0;
}
This works when line == "1 2"
(prints "n==1,n==2,"
) or line == "1"
(prints "n==1"
).
However, when line
has trailing whitespace, for example when line == "1 2 "
(note the "space" character after the digit), the last number somehow gets read twice. In this example, the program prints "n==1,n==2,n==2,"
. I think the reason this happens is that the variable n
is stored at the same memory address every iteration, and since I didn't initialize it, it uses the value of n
from the previous iteration.
To test this, I have modified the for loop:
for (char nextChar = lineStream.peek(); nextChar != EOF; nextChar = lineStream.peek()) {
int n = 0;
lineStream >> n;
std::cout << "n==" << n << "," << std::flush;
}
And just as I suspected, now having line == "1 2 "
prints "n==1,n==2,n==0,"
.
From that I deduce that I have an extra iteration in the case of an input with trailing whitespace. However, I am not sure how to fix it; I can't just break out of the loop when nextChar
is whitespace, since there are whitespace characters between every two numbers.
So my question is:
How can I avoid the extra iteration of the for loop in the case of a trailing whitespace? Note that I have to read the input one line at a time, so using std::cin
directly isn't an option to my understanding.
I am using C++17.
Upvotes: 0
Views: 86
Reputation: 37512
This is common mistake beginners do when using C++ streams: checking for end of file explicitly.
There is better way, check if extraction operation is successful:
int n;
while (lineStream >> n) {
std::cout << "n==" << n << ","; // do not do "<< std::flush;" unless is relay needed.
}
Note that this will work with input you mentioned, but also in case of "1 2a 3"
will print n=1,n=2,
- it stops as soon as it can't read next integer value (int this case stops on a
).
Upvotes: 2
Reputation: 411
As 463035818_is_not_an_ai said in the comments, you should also check the the state of the stream after the assignment, rather that only checking for EOF.
One way to do this is changing the loop body like so:
for (char nextChar = lineStream.peek(); nextChar != EOF; nextChar = lineStream.peek()) {
int n;
if (!(lineStream >> n)) break; // If the read wasn't successful, break out of the loop
std::cout << "n==" << n << "," << std::flush; // If the read was successful, use the value
}
If line == "1 2 ", this will print
"1,2,"` as expected.
As Hatted Rooster written in this SO answer:
operator>>
returns a reference to the stream, as you said. Then, in the context of theif
the stream is converted to abool
through the conversion operator which gives:true if the stream has no errors, false otherwise.
An invalid input is considered an error, so linestream >> n
will return false
in this case. After trying to assign the trailing whitespace into an int
and failing, false
will be returned and the loop will stop before using the invalid value.
You can also use the input stream's fail()
method, which like the >>
operator, will return "true
if an error has occurred, false
otherwise". So you can also write your code like so:
for (char nextChar = lineStream.peek(); nextChar != EOF; nextChar = lineStream.peek()) {
int n;
lineStream >> n;
if (lineStream.fail()) {
std::cout << "(invalid input)" << "," << std::flush;
} else {
std::cout << "n==" << n << "," << std::flush;
}
}
If line == "1 2 ", this will print
"n==1,n==2,(invalid input),"`.
Upvotes: 0