Search code examples
c++exceptiongetlineeof

getline setting failbit along with eof


I am aware of the origin of this behavior since it has been very well explained in multiple posts here in SO, some notable examples are:

Why is iostream::eof inside a loop condition considered wrong?

Use getline() without setting failbit

std::getline throwing when it hits eof

C++ istream EOF does not guarantee failbit?

And it is also included in the std::getline standard:

3) If no characters were extracted for whatever reason (not even the discarded delimiter), getline sets failbit and returns.

My question is how does one deal with this behavior, where you want your stream to catch a failbit exception for all cases except the one caused by reaching the eof, of a file with an empty last line. Is there something obvious that I am missing?

A MWE:

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>


void f(const std::string & file_name, char comment) {

std::ifstream file(file_name);
file.exceptions(file.failbit);
    try {
          std::string line;

          while (std::getline(file, line).good()) {
          // empty getline sets failbit throwing an exception
            if ((line[0] != comment) && (line.size() != 0)) {
                std::stringstream ss(line);
                // do stuff
            }
        }
    }

    catch (const std::ios_base::failure& e) {
        std::cerr << "Caught an ios_base::failure.\n"
        << "Explanatory string: " << e.what() << '\n'
        << "Error code: " << e.code() << '\n';

        }
}


int main() {

    f("example.txt", '#');
}

where example.txt is a tab-delimited file, with its last line being only the \n char:

# This is a text file meant for testing
0   9
1   8
2   7

EDIT:

while(std::getline(file, line).good()){...} replicates the problem.


Solution

  • Another way to avoid setting failbit, is simply to refactor your if tests to detect the read of an empty-line. Since that is your final line in this case, you can simply return to avoid throwing the error, e.g.:

        std::ifstream file (file_name);
        file.exceptions (file.failbit);
        try {
            std::string line;
    
            while (std::getline(file, line)) {
                // detect empty line and return
                if (line.size() == 0)
                    return;
                if (line[0] != comment) {
                    std::stringstream ss(line);
                    // do stuff
                }
            }
        }
        ...
    

    You other alternative is to check whether eofbit is set in catch. If eofbit is set -- the read completed successfully. E.g.

        catch (const std::ios_base::failure& e) {
            if (!file.eof())
                std::cerr << "Caught an ios_base::failure.\n"
                << "Explanatory string: " << e.what() << '\n'
                << "Error code: " /* << e.code() */ << '\n';
        }