0

How can you write all but the first N lines of a stream without corrupting the destination stream?

For example, the following works fine when the source has n-1 or less lines and when source has n+1 or more lines, but sometimes fails when the source has exactly n lines:

void copy_all_but_first_n_lines(std::ifstream& source, std::ofstream& dest, size_t n)
{
    for (size_t i=0; i<n; ++i)
    {
        std::string line;
        std::getline(source, line);
    }

    if (source.good()) dest << source.rdbuf();

    if (!dest.good()) throw std::runtime_error("destination turned bad after writing remainder of source");
}

With a source with exactly n lines, destination sometimes has the failbit set.

This failbit is only set when the source has a trailing newline. I am testing this on Windows, and in a hex editor I see that files with a trailing newline and exactly n lines cause the destination stream to have the failbit set, but files with exactly n lines without a trailing end of line don't result in the failbit set. All the files I'm testing with have newlines as "\r\n".

I tried opening the streams in both text and binary mode, but that didn't change the behavior.


If I change the code to use std::copy instead of writing the rdbuf, it works regardless of whether or not there is a trailing newline. According to this, these two should be equivalent -- why is writing rdbuf failing while std::copy succeeds?

void copy_all_but_first_n_lines(std::ifstream& source, std::ofstream& dest, size_t n)
{
    for (size_t i=0; i<n; ++i)
    {
        std::string line;
        std::getline(source, line);
    }

    if (source.good())
    {
        std::istreambuf_iterator<char> begin(source);
        std::istreambuf_iterator<char> end;
        std::ostreambuf_iterator<char> destination(dest);
        std::copy(begin, end, destination);
    }

    if (!dest.good()) throw std::runtime_error("destination turned bad after writing remainder of source");
}
Community
  • 1
  • 1
JDiMatteo
  • 12,022
  • 5
  • 54
  • 65

1 Answers1

2

Take a look at the standard:

basic_ostream<charT,traits>& operator<<(basic_streambuf<charT,traits>* sb);

7 Effects: Behaves as an unformatted output function (as described in 27.7.3.7, paragraph 1). After the sentry object is constructed, if sb is null calls setstate(badbit) (which may throw ios_base::failure).
8 Gets characters from sb and inserts them in *this. Characters are read from sb and inserted until any of the following occurs:

  • end-of-file occurs on the input sequence;
  • inserting in the output sequence fails (in which case the character to be inserted is not extracted);
  • an exception occurs while getting a character from sb.

9 If the function inserts no characters, it calls setstate(failbit) (which may throw ios_base::failure (27.5.5.4)). If an exception was thrown while extracting a character, the function sets failbit in error state, and if failbit is on in exceptions() the caught exception is rethrown.

So, they are only equivalent if at least one character will be copied.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118