istream operator >> not recognising '\n' character - c++

I am basically reading a .txt file and storing values.
For example:
Student- Mark
Tennis
It will store Mark into memory as the studentName.
Now...If it is just
Student-
Tennis
Then it will work fine and produce an error.
However, if the file looks like this
Student-(space)(nothing here)
Tennis
It will store Tennis into memory as the studentName, when if fact it should store nothing and produce an error. I use '\n' character to determine if there is anything after the - character. This is my code...
istream& operator>> (istream& is, Student& student)
{
is.get(buffer,200,'-');
is.get(ch);
if(is.peek() == '\n')
{
cout << "Nothing there" << endl;
}
is >> ws;
is.getline(student.studentName, 75);
}
I think it is because the is.peek() is recognizing white space, but then if I try removing white space using is >> ws, it removes the '\n' character and still stores Tennis as the studentName.
Would really mean a lot if someone could help me solve this problem.

If you want to ignore whitespace but not '\n' you can't use std::ws as easily: it will skip over all whitespace and aside from ' ' the characters '\n' (newline), '\t' (tab), and '\r' (carriage return) are considered whitespace (I think there are actually even a few more). You could redefine what whitespace means for your stream (by replacing the stream's std::locale with a custom std::ctype<char> facet which has changed idea of what whitespace means) but that's probably a bit more advanced (as far as I can tell, there is about a handful of people who could do that right away; ask about it and I'll answer that question if I notice it, though...). An easier approach is to simply read the tail of the line using std::getline() and see what's in there.
Another alternative is create your own manipulator, let's say, skipspace, and use that prior to checking for newline:
std::istream& skipspace(std::istream& in) {
std::istreambuf_iterator<char> it(in), end;
std::find_if(it, end, [](char c){ return c != ' '; });
return in;
}
// ...
if ((in >> skipspace).peek() != '\n') {
// ....
}

You don't need to peek characters. I would use std::getline() and let it handle line breaks for you, then use std::istringstream for parsing:
std::istream& operator>> (std::istream& is, Student& student)
{
std::string line;
if (!std::getline(is, line))
{
std::cout << "Can't read student name" << std::endl;
return is;
}
std::istringstream iss(line);
std::string ignore;
std::getline(iss, ignore, '-');
iss >> std::ws;
iss.getline(student.studentName, 75);
/*
read and store className if needed ...
if (!std::getline(is, line))
{
std::cout << "Can't read class name" << std::endl;
return is;
}
std::istringstream iss2(line);
iss2.getline(student.className, ...);
*/
return is;
}
Or, if you can change Student::studentName into a std::string instead of a char[]:
std::istream& operator>> (std::istream& is, Student& student)
{
std::string line;
if (!std::getline(is, line))
{
std::cout << "Can't read student name" << std::endl;
return is;
}
std::istringstream iss(line);
std::string ignore;
std::getline(iss, ignore, '-');
iss >> std::ws >> student.studentName;
/*
read and store className if needed ...
if (!std::getline(is, student.className))
{
std::cout << "Can't read class name" << std::endl;
return is;
}
*/
return is;
}

Related

How to write an `std::istream` operator

How do I write a function that reads an std::istream and sets proper flags if the stream contained unexpected content, ended before expected, or was not fully consumed?
For concreteness, suppose I'm expecting the stream to contain a string of alpha characters followed by a separator and then some digits, like foo:55. I'd like to read something like
struct var {
std::string name;
double value;
};
from the stream. I can of course write the operator as
std::istream& operator>>(std::istream& s, var& x) {
std::string str;
s >> str;
size_t sep = str.find(':');
x.name = str.substr(0,sep);
x.value = atof(str.substr(sep+1).c_str());
return s;
}
But can I do without copying the stream content to a string? Also, this doesn't work with spaces, in the sense that str won't contain the whole stream content.
I asked a similar question about a week ago, but there was no response to it, probably because I framed it in context on boost::program_options and such questions don't seem to get much attention here.
You can use std::getline instead of s >> str to read up to ':', and then read the number directly into the double, like this:
std::istream& operator>>(std::istream& s, var& x) {
// Skip over the leading whitespace
while (s.peek() == '\n' || s.peek() == ' ') {
s.get();
}
std::getline(s, x.name, ':');
s >> x.value;
return s;
}
Demo.
Why not let the stream do the work for you. You can use getline(), >> and istream::ignore() to read in the input.
std::istream& operator>>(std::istream& s, var& x) {
// get the string part and through out the :
std::getline(s, x.name, ':');
// get the number part
s >> x.value;
// consume the newline so the next call to getline won't include it in the string part
s.ignore(std::numeric_limits<std::streamsize>::max(), '\n')
return s;
}

Reading from the Istream, how to first read one word then an entire line, and return it?

I have the following struct:
struct Person{
std::string name;
std::string address;
std::string& personName(){ return name; }
std::string& personAddress(){return address;}
};
The exercise is to write a read function that will read name and address. For example, the function I first wrote was this:
std::istream &read(std::istream &is, Person &person){
is >> person.name >> person.address;
return is;
}
However this function fails to take more than a word for address. For example if input is:
Lee Goswell Road
The output will be person.name = "Lee" and person.address = "Goswell". What I want is the function to read the entire address basically. I did try solving this problem as follows, but I am not sure it is right because address is changed implicitly:
std::istream &read(std::istream &is, Person &person){
is >> person.name;
std::getline(std::cin, person.address);
return is;
}
Another thing to consider before saying I should write separate functions, the task is to write one function to take read both the name and address.
You can use operator>> in tandem with std::getline but you'll probably want to eat the white-space from the stream first.
Also rather than read, you should just create your own operator>>:
std::istream& operator>>(std::istream& is, Person& person){
is >> person.name >> std::ws;
std::getline(is, person.address);
return is;
}
You can then use this as follows:
std::istringstream foo("Lee Goswell Road\nJon Lois Lane");
Person bar;
foo >> bar;
std::cout << bar.name << std::endl << bar.address << std::endl;
Just read a word, skip leading whitespace, then read to a delimiter:
if (is >> person.name >> std::ws
std::getline(is, person.address)) {
// do something with the input
}
else {
// deal with input failure
}
std::ws simply skips leading whitespace and std::getline() reads to delimiter with '\n' being the default.

Overloading operator >> and << so that it accepts user-defined object

I've got an object (of class myObj) that contains multiple strings (a string pointer). I want to overload the >>operator so that I can read in multiple strings at a time.
This overloaded operator functions accept statements like:
cin >> str;
cout << str;
The only problem is that when I fill in a series of strings, it seems that only the first string gets correctly processed in the stream.
To insert:
istream &operator>>(istream &is, myObj &obj)
{
std::string line;
while (true)
{
std::getline(is, line);
if (not is)
break;
obj.add(line);
is >> line;
}
return is;
}
To extract
ostream &operator<<(ostream &os, myObj const &obj)
{
for(size_t idx = 0; idx != obj.size(); ++idx)
os << obj[idx];
return os;
}
The code compiles fine but when I cout the object only the first string is printed and the other strings are omitted.
So when I provide cin with:
Hi
Stack
Exchange
only Hi will be displayed.
Does anyone know why this happens?
Thanks in advance!
P.S I am new to Stack Exchange and I am working hard to formulate the problems as best as I can :)
Your loop will work like that:
std::getline(is, line);
Extracts and stores "Hi" in line, extracts the newline
obj.add(line);
Adds "Hi" to obj
is >> line;
Extracts and stores "Stack" in line, does not extracts the following newline
std::getline(is, line);
Extracts and stores an empty string in line, because next read char is a newline
obj.add(line);
Adds empty string "" to obj
is >> line;
Extracts and stores "Exchange" in line
std::getline(is, line);
Extracts nothing (end of input stream)
if (not is)
break;
Then the stream is at end, your loop exits.
Conclusion : you stored only Hi and an empty string
is >> line puts a leading newline in the stream which is being read by std::getline() during the following loop. Because std::getline() stops input when it finds a newline, this reads an empty string into line and thus the stream is put into an error state which the loop responds to by breaking out of it.
There doesn't seem to be a need for that last read. You can remove it.
Also, this is a more idiomatic way to loop with input. This way you don't have to check the state of the stream within the loop body:
for (std::string line; std::getline(is, line); )
{
obj.add(line);
}
And since there's only one token per line, you can use a formmatted extractor:
for (std::string word; is >> word; )
{
obj.add(word);
}

Avoid operator>> to break input at whitespaces

I'm overloading the operator>> function. It should take a string in input, with some whitespaces needed, explode the string at whitespaces and do other operations not relevant for the topic.
I have this code:
std::istream& operator>>(std::istream &in, Foo &f) {
std::string str;
in >> str;
std::cout << "str = " << str << std::endl; // for testing
// ...
return in;
}
Assuming to put this string (a complex number) as input:
3 + 2i
the std::cout function prints only 3. I tried to put the flag std::noskipws, but the problem is still there.
Is there any way to solve this issue?
Use std::getline function to read complete input line:
std::getline(in, str);

Modify cin to also return the newlines

I know about getline() but it would be nice if cin could return \n when encountered.
Any way for achieving this (or similar)?
edit (example):
string s;
while(cin>>s){
if(s == "\n")
cout<<"newline! ";
else
cout<<s<<" ";
}
input file txt:
hola, em dic pere
caram, jo també .
the end result shoud be like:
hola, em dic pere newline! caram, jo també .
If you are reading individual lines, you know that there is a newline after each read line. Well, except for the last line in the file which doesn't have to be delimited by a newline character for the read to be successful but you can detect if there is newline by checking eof(): if std::getline() was successful but eof() is set, the last line didn't contain a newline. Obviously, this requires the use of the std::string version of std::getline():
for (std::string line; std::getline(in, line); )
{
std::cout << line << (in.eof()? "": "\n");
}
This should write the stream to std::cout as it was read.
The question asked for the data to be output but with newlines converted to say "newline!". You can achieve this with:
for (std::string line; std::getline(in, line); )
{
std::cout << line << (in.eof()? "": "newline! ");
}
If you don't care about the stream being split into line but actually just want to get the entire file (including all newlines), you can just read the stream into a std::string:
std::string file((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>());
Note, however, that this exact approach is probably fairly slow (although I know that it can be made fast). If you know that the file doesn't contain a certain character, you can also use std::getline() to read the entire file into a std::string:
std::getline(in, file, 0);
The above code assumes that your file doesn't contain any null characters.
A modification of #Dietmar's answer should do the trick:
for (std::string line; std::getline(in, line); )
{
std::istringstream iss(line);
for (std::string word; iss >> word; ) { std::cout << word << " "; }
if (in.eof()) { std::cout << "newline! "; }
}
Just for the record, I ended up using this (I wanted to post it 11h ago)
string s0, s1;
while(getline(cin,s0)){
istringstream is(s0);
while(is>>s1){
cout<<s1<<" ";
}
cout<<"newline! ";
}