Note: please read the comments before answering. The problem seems to be compiler specific.
I have a simple program which reads a name and some grades from a file or the console into a struct Student_info, and then prints out some of the data by overloading the << and >> operators. However, the program is cutting off parts of or even entire words and shifting the data. For example, the input
Eunice 29 87 42 33 18 13
Mary 71 24 3 96 70 14
Carl 61 12 10 44 82 36
Debbie 25 42 53 63 34 95
returns
Eunice: 42 33 18 13
Mary: 3 96 70 14
rl: 10 44 82 36
25: 63 34 95
suggesting that somehow, the stream has ignored the first two letters of Carl, and then shifted the entire stream left 1 word. I've been trying to debug this for the better part of an hour, but it seems arbitrary. For different names, different words get cut off, with no apparent pattern.
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
struct Student_info {
friend std::ostream &operator<<( std::ostream &output,
const Student_info &S ) { // overloads operator to print name and grades
output << S.name << ": ";
for (auto it = S.homework.begin(); it != S.homework.end(); ++it)
std::cout << *it << ' ';
return output;
}
friend std::istream &operator>>( std::istream &input, Student_info &S ) { // overloads operator to read into Student_info object
input >> S.name >> S.midterm >> S.final;
double x;
if (input) {
S.homework.clear();
while (input >> x) {
S.homework.push_back(x);
}
input.clear();
}
return input;
}
std::string name; // student name
double midterm, final; // student exam scores
std::vector<double> homework; // student homework total score (mean or median)
};
int main() {
//std::ifstream myfile ("/Users/.../Documents/C++/example_short.txt");
Student_info student; // temp object for receiving data from istream
std::vector<Student_info> student_list; // list of students and their test grades
while (std::cin >> student) { // or myfile >> student
student_list.push_back(student);
student.homework.clear();
}
for (auto it = student_list.begin(); it != student_list.end(); ++it) {
std::cout << *it << '\n';
}
return 0;
}
edit: added newline character.
As you can see it doesn't work with clang, but it does with GCC
There is an inconsistency between implementations of how they implement the standard when it comes to failed floating point formatted input.
clang
(or more precisely, libc++
) reads and discards all characters that a valid floating point representation could possibly contain, even if it cannot contain them at this position and the conversion will necessarily fail. These characters include C
and A
(both upper and lower case, because they are hexadecimal digits and hexadecimal floating point number notation is actually allowed by the standard). Thus, when trying to read a double
and the input contains Carl
, the characters C
and A
are read and discarded, even though no floating point number can start with either of these characters.
On the other hand, gcc
(libstdc++
) stops reading as soon as it becomes clear that the conversion will fail. Thus, if the input contains Carl
, the conversion stops at the first character (and it is retained in the stream), because a hexadecimal floating point number cannot start with C
(it must start with 0X
).
I won't volunteer an opinion about which implementation is formally correct. Whatever it may be, normal code should steer clear of subtle and mysterious corners of the language. If a student record is a line, then the code should read lines. If a student record is defined as "a name and a sequence of numbers that last until the next name", then stop and read this article.
I think the problem is while parsing the input with the end of line
and the double
.
I found 2 ways to solve it:
Read homework until the end of line.
friend std::istream &operator>>(std::istream &input, Student_info &S)
{ // overloads operator to read into Student_info object
input >> S.name >> S.midterm >> S.final;
double x;
if (input)
{
S.homework.clear();
while ((input.peek()!='\n') && input >> x)
{
//std::cout << x << "\n";
S.homework.push_back(x);
}
input.clear();
}
return input;
}
Do not parse it as double if you know the inputs would be integers
friend std::istream &operator>>(std::istream &input, Student_info &S)
{ // overloads operator to read into Student_info object
input >> S.name >> S.midterm >> S.final;
int x;
if (input)
{
S.homework.clear();
while (input >> x)
{
//std::cout << x << "\n";
S.homework.push_back(x);
}
input.clear();
}
return input;
}
When you know that information for a Student_Info
object is always in a single line, it is better to use the following strategy.
std::istringstream
from the line of text.Student_Info
from the std::istringstream
.It just makes the parsing code simpler and less error prone.
// overloads operator to read into Student_info object
friend std::istream &operator>>( std::istream &input, Student_info &S )
{
std::string line;
if ( !getline(input, line) )
{
// Problem reading a line of text.
return input;
}
std::istringstream str(line);
str >> S.name >> S.midterm >> S.final;
S.homework.clear();
double x;
while ( str >> x)
{
S.homework.push_back(x);
}
return input;
}
FWIW, I can't duplicate the problem you are seeing. Check out a working version at http://ideone.com/13wHLa.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With