C++ tellg() doesn't work with getline()?
Asked Answered
Q

2

6

I know the title sounds crazy, but I'm experiencing this firsthand right now and I can't think of any reason why this is failing.

I am reading through a file using getline()

At the end of the reading, I call tellg(). However, this call always fails (return value of -1).

Is it a known issue that tellg() doesn't work with getline() or am I doing something else wrong?

The code I am using is very simple, basically

while(getline(file,line))
{
//tokenize and do other things
}
cout<<file.tellg()<<endl;

The file in question is a simple txt file on a regular disk, I tried a file with and without CRLF and it makes no difference.

EDIT: Additional information

gcc/g++ 4.1.2, Linux (RHEL 5)

EDIT2: According to this thread: http://www.cplusplus.com/forum/beginner/3599/#msg15540 It is impossible to use tellg with getline due to some sort of gcc bug. Is this actually the case? (what you read on the internet is not always true =P)

Quay answered 26/6, 2013 at 1:21 Comment(2)
Use file.clear() before your file.tellg() to clear the failbit.Tracitracie
cplusplus.com is not a reliable source. Its beginner forums are doubly so.Tseng
M
7

The tellg() function works by attempting to construct a sentry object and then checking for the failbit before returning a proper value. If the failbit is set, it returns -1. Details can be found here or, if you prefer a more official source and don't mind a dry read, the ISO C++ standard (27.6.1.3/36a-37 for C++03, 27.7.2.3/39-40 for C++11).

The construction of the sentry first checks any of the error flags (like eofbit) and, if set, it sets the failbit and returns. See here for detail (C++03 27.6.1.1.2, C++11 27.7.2.1.3),

Hence a tellg() after the end of file flag has been set will fail. The fact that you're reading lines until getline returns false means that the stream's eofbit is being set, hence you've reached the end of the file.

You can see the behavior with this following program:

#include <iostream>
#include <iomanip>

int main (void) {
    std::string line;
    while (std::getline (std::cin, line)) {
        if (line.length() > 20)
            line = line.substr(0,17) + "...";
        std::cout << "tellg() returned "
            << std::setw(5) << std::cin.tellg()
            << " after " << line << "\n";
    }
    //std::cin.clear();
    std::cout << "tellg() returns: "
        << std::cin.tellg() << '\n';
    return 0;
}

When you run that and provide the file itself as input, you see:

tellg() returned    20 after #include <iostream>
tellg() returned    39 after #include <iomanip>
tellg() returned    40 after 
tellg() returned    58 after int main (void) {
tellg() returned    80 after     std::string l...
tellg() returned   124 after     while (std::g...
tellg() returned   156 after         if (line....
tellg() returned   202 after             line ...
tellg() returned   243 after         std::cout...
tellg() returned   291 after             << st...
tellg() returned   333 after             << " ...
tellg() returned   339 after     }
tellg() returned   363 after     //std::cin.cl...
tellg() returned   400 after     std::cout << ...
tellg() returned   437 after         << std::c...
tellg() returned   451 after     return 0;
tellg() returned   453 after }
tellg() returned   454 after 
tellg() returns: -1

If you uncomment the line in that code which clears the error state variables, it will work:

tellg() returned    20 after #include <iostream>
tellg() returned    39 after #include <iomanip>
tellg() returned    40 after 
tellg() returned    58 after int main (void) {
tellg() returned    80 after     std::string l...
tellg() returned   124 after     while (std::g...
tellg() returned   156 after         if (line....
tellg() returned   202 after             line ...
tellg() returned   243 after         std::cout...
tellg() returned   291 after             << st...
tellg() returned   333 after             << " ...
tellg() returned   339 after     }
tellg() returned   361 after     std::cin.clea...
tellg() returned   398 after     std::cout << ...
tellg() returned   435 after         << std::c...
tellg() returned   449 after     return 0;
tellg() returned   451 after }
tellg() returned   452 after 
tellg() returns: 452

And, as an aside, it looks like the bug you're referring to may be this one (it's a little unclear since the post you linked to is sadly missing any detail - it would have been better had the poster bothered to support his assertion that it was a known bug by, for example, linking to it).

If that's the case, the first thing you should notice is that it was fixed more than a decade ago so, unless you're using an absolutely ancient gcc, it's not going to be an issue now.

Melancholia answered 26/6, 2013 at 1:27 Comment(5)
I'm not actually calling close() on the input stream before calling tellg(). You're saying getline() sets the failbit after it runs through to the end of the file?Quay
More specifically, getline will set the failbit when it extracts no characters, which will be typical at the end of file.Tracitracie
Even if getline doesn't set failbit, it will set eofbit at end of file, and the sentry construction will set failbit because of that.Melancholia
The stream is not closed until its buffer's close function or destructor is called. To make tellg work again, just call clear.Tseng
@Potatoswatter, correct, I've adjusted the answer to say the eof bit is set rather than the file is closed, and added the solution. I also confirmed - both eofbit and failbit are set when the loop exits, badbit is not.Melancholia
T
0

std::istream::tellg does not tell you anything if the stream's error flag is set. According to its spec,

Returns: After constructing a sentry object, if fail() != false, returns pos_type(-1) to indicate failure. Otherwise, returns rdbuf()->pubseekoff(0, cur, in).

Referring to std::istream::sentry, it sets fail if eof is already set.

But fail and eof are cleared by the clear function, which is all you need to do.

while(getline(file,line))
{
//tokenize and do other things
}
file.clear(); // reset error state
cout<<file.tellg()<<endl;

And the pubseekoff function still works even if you don't bother with clear, so this works too:

cout<< static_cast< std::streamoff >( file.rdbuf()->pubseekoff(0, std::ios::cur, std::ios::in) )
    <<endl;
Tseng answered 26/6, 2013 at 1:58 Comment(1)
@Melancholia Thanks, I've been a zombie today.Tseng

© 2022 - 2024 — McMap. All rights reserved.