What would be the best way to get the line number of the current line in a file that I have opened with a ifstream? So I am reading in the data and I need to store the line number that it is on so that I can display it later if the data doesn't match the specifications.
4 Answers
If you don't want to limit yourself to std::getline, then you could use class derived from std::streambuf, and which keeps track of the current line number:
class CountingStreamBuffer : public std::streambuf { /* see below */ }; // open file std::ifstream file("somefile.txt"); // "pipe" through counting stream buffer CountingStreamBuffer cntstreambuf(file.rdbuf()); std::istream is(&cntstreambuf); // sample usage is >> x >> y >> z; cout << "At line " << cntstreambuf.lineNumber(); std::getline(is, str); cout << "At line " << cntstreambuf.lineNumber(); Here is a sample implementation of CountingStreamBuffer:
#include <streambuf> class CountingStreamBuffer : public std::streambuf { public: // constructor CountingStreamBuffer(std::streambuf* sbuf) : streamBuf_(sbuf), lineNumber_(1), lastLineNumber_(1), column_(0), prevColumn_(static_cast<unsigned int>(-1)), filePos_(0) { } // Get current line number unsigned int lineNumber() const { return lineNumber_; } // Get line number of previously read character unsigned int prevLineNumber() const { return lastLineNumber_; } // Get current column unsigned int column() const { return column_; } // Get file position std::streamsize filepos() const { return filePos_; } protected: CountingStreamBuffer(const CountingStreamBuffer&); CountingStreamBuffer& operator=(const CountingStreamBuffer&); // extract next character from stream w/o advancing read pos std::streambuf::int_type underflow() { return streamBuf_->sgetc(); } // extract next character from stream std::streambuf::int_type uflow() { int_type rc = streamBuf_->sbumpc(); lastLineNumber_ = lineNumber_; if (traits_type::eq_int_type(rc, traits_type::to_int_type('\n'))) { ++lineNumber_; prevColumn_ = column_ + 1; column_ = static_cast<unsigned int>(-1); } ++column_; ++filePos_; return rc; } // put back last character std::streambuf::int_type pbackfail(std::streambuf::int_type c) { if (traits_type::eq_int_type(c, traits_type::to_int_type('\n'))) { --lineNumber_; lastLineNumber_ = lineNumber_; column_ = prevColumn_; prevColumn_ = 0; } --column_; --filePos_; if (c != traits_type::eof()) return streamBuf_->sputbackc(traits_type::to_char_type(c)); else return streamBuf_->sungetc(); } // change position by offset, according to way and mode virtual std::ios::pos_type seekoff(std::ios::off_type pos, std::ios_base::seekdir dir, std::ios_base::openmode mode) { if (dir == std::ios_base::beg && pos == static_cast<std::ios::off_type>(0)) { lastLineNumber_ = 1; lineNumber_ = 1; column_ = 0; prevColumn_ = static_cast<unsigned int>(-1); filePos_ = 0; return streamBuf_->pubseekoff(pos, dir, mode); } else return std::streambuf::seekoff(pos, dir, mode); } // change to specified position, according to mode virtual std::ios::pos_type seekpos(std::ios::pos_type pos, std::ios_base::openmode mode) { if (pos == static_cast<std::ios::pos_type>(0)) { lastLineNumber_ = 1; lineNumber_ = 1; column_ = 0; prevColumn_ = static_cast<unsigned int>(-1); filePos_ = 0; return streamBuf_->pubseekpos(pos, mode); } else return std::streambuf::seekpos(pos, mode); } private: std::streambuf* streamBuf_; // hosted streambuffer unsigned int lineNumber_; // current line number unsigned int lastLineNumber_;// line number of last read character unsigned int column_; // current column unsigned int prevColumn_; // previous column std::streamsize filePos_; // file position }; Comments
From an ifstream point of view there is no line number. If you read in the file line by line, then you just have to keep track of it yourself.
4 Comments
std::getline) or simply count the number of \n characters that pass by.\n signifies a newline on your platform. I find it better to track the calls to newline (a simple wrapper will do...)\n represents a new line, the iostream uses the locale to make it so (both reading and writing). If you read in binary mode, of course, then no conversion occurs...\r\n (not \n, just open any unix source file with notepad to see the difference), and the locale takes care of that too...An inefficient but dead simple way is to have a function that given a stream, it counts the new line characters from the beginning of the stream to the current position.
int getCurrentLine(std::istream& is) { int lineCount = 1; is.clear(); // need to clear error bits otherwise tellg returns -1. auto originalPos = is.tellg(); if (originalPos < 0) return -1; is.seekg(0); char c; while ((is.tellg() < originalPos) && is.get(c)) { if (c == '\n') ++lineCount; } return lineCount; } In some code I am working on, I am only interested to know the line number if invalid input is encountered, in which case import is aborted immediately. Since the function is called only once the inefficiency is not really a problem.
The following is a full example:
#include <iostream> #include <sstream> int getCurrentLine(std::istream& is) { int lineCount = 1; is.clear(); // need to clear error bits otherwise tellg returns -1. auto originalPos = is.tellg(); if (originalPos < 0) return -1; is.seekg(0); char c; while ((is.tellg() < originalPos) && is.get(c)) { if (c == '\n') ++lineCount; } return lineCount; } void ReadDataFromStream(std::istream& s) { double x, y, z; while (!s.fail() && !s.eof()) { s >> x >> y >> z; if (!s.fail()) std::cout << x << "," << y << "," << z << "\n"; } if (s.fail()) std::cout << "Error at line: " << getCurrentLine(s) << "\n"; else std::cout << "Read until line: " << getCurrentLine(s) << "\n"; } int main(int argc, char* argv[]) { std::stringstream s; s << "0.0 0.0 0.0\n"; s << "1.0 ??? 0.0\n"; s << "0.0 1.0 0.0\n"; ReadDataFromStream(s); std::stringstream s2; s2 << "0.0 0.0 0.0\n"; s2 << "1.0 0.0 0.0\n"; s2 << "0.0 1.0 0.0"; ReadDataFromStream(s2); return 0; }