c++filefile-pointer

Why does seekp() fail in this case?


I am using this program to modify every vowel in text.txt to become the hashtag ('#')

#include <fstream>
#include <iostream>

int main()
{
    std::fstream iofile{ "text.txt", std::ios::in | std::ios::out };

    char chChar;

    while (iofile.get(chChar))
    {
        switch (chChar)
        {
        case 'a':
        case 'e':
        case 'i':
        case 'o':
        case 'u':
        case 'A':
        case 'E':
        case 'I':
        case 'O':
        case 'U':

            iofile.seekp(-1, std::ios::cur);

            iofile << '#';

            iofile.seekg(iofile.tellg(), std::ios::beg); 
        }
    }
}

So if the initial content of text.txt is

something is funny

It will turn to

s#m#th#ng #s f#nny

The problem is this line

iofile.seekg(iofile.tellg(), std::ios::beg); 

This line keeps the file pointer in the same position so I thought I might change the line to this

iofile.seekg(0, std::ios::cur);

I thought this will do the same thing but it didn't. When I run the program the console just stops there and doesn't stop, and text.txt starts printing weird things. But when I replace it with this line, it works again

iofile.seekg(1, std::ios::cur);

I thought this should move the file pointer too far (we should remain it in the position but here we move it pass 1 letter). However, this works. What is happening?


Solution

  • It's a good question! I was able to reproduce your issue using Microsoft Visual Studio Community Edition 2019. However, using Visual Studio Code and g++ 9.3.0, I didn't get the same problem. It seems to be a compiler issue, specifically with Microsoft Visual C++.

    The C++ standard refers to the C standard library regarding restrictions on file streams:

    The restrictions on reading and writing a sequence controlled by an object of class basic_­filebuf<charT, traits> are the same as for reading and writing with the C standard library FILEs.

    The C standard library (7.19.5.3 paragraph 6) says:

    When a file is opened with update mode ('+' as the second or third character in the above list of mode argument values), both input and output may be performed on the associated stream. However, output shall not be directly followed by input without an intervening call to the fflush function or to a file positioning function (fseek, fsetpos, or rewind), and input shall not be directly followed by output without an intervening call to a file positioning function, unless the input operation encounters endof-file.

    In other words: you have to perform a flush or seek when switching between input and output for a filestream. Now, this line:

    iofile.seekg(0, std::ios::cur);
    

    should perform a seek (to the same position relative to the current stream position), but with MSVC++ that doesn't seem to be happening, and undefined behaviour subsequently follows. However, when you seek from the beginning of the file by specifying std::ios::beg a seek is actually performed and your program works as expected.