Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is iostream cutting off the first letter in certain words?

Tags:

c++

iostream

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

like image 444
JAustin Avatar asked Jun 22 '17 01:06

JAustin


3 Answers

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.

like image 85
n. 1.8e9-where's-my-share m. Avatar answered Sep 22 '22 00:09

n. 1.8e9-where's-my-share m.


I think the problem is while parsing the input with the end of line and the double. I found 2 ways to solve it:

  1. 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;
    }
    
  2. 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;
    }
    
like image 45
Fede Avatar answered Sep 18 '22 00:09

Fede


When you know that information for a Student_Info object is always in a single line, it is better to use the following strategy.

  1. Read a line of text.
  2. Construct a std::istringstream from the line of text.
  3. Extract the data corresponding to a 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.

like image 37
R Sahu Avatar answered Sep 18 '22 00:09

R Sahu