c++c++17streambuf

Sanity check: Does returning !EOF from streambuf-derived class' overflow function make sense?


I can find surprisingly little useful information about how an overridden std::streambuf::overflow function should look like to simply get each character that's written to the stream. So I asked ChatGPT for some pointers. It keeps coming back to this concept:

int overflow(int c)
{
    if (c == EOF)
    {
        // Flush the buffer here
        return !EOF;
    }
    // Put c in the buffer here
    if (c == '\n')
    {
        // Flush the buffer here
    }
    return c;
}

It's super weird that it returns !EOF when c is EOF.
The docs don't elaborate what "success" means.
This page says it should return EOF (not !EOF) when called with EOF as the argument (or when signalling "a failure").

So: Is my suspicion correct that returning !EOF here is wrong and that I should return EOF instead?
Cookie points if you can tell me where ChatGPT got that idea from. I can't find return !EOF; anywhere else on the internet.


Solution

  • This is what the C++ standard has to say about the return value:

    [streambuf.virt.put]

    int_type overflow(int_type c = traits::eof());
    

    6 Returns: traits::eof() or throws an exception if the function fails.
    Otherwise, returns some value other than traits::eof() to indicate success.(304)

    (Footnote 304) Typically, overflow returns c to indicate success, except when traits::eq_int_type(c, traits::eof()) returns true, in which case it returns traits::not_eof(c).

    Thus, the specific return value doesn't much matter, just whether it's traits::eof() or not. There's a (non-normative) convention you may want to follow.

    Re: what "success" means:

    [streambuf.virt.put]/5 Requires: Every overriding definition of this virtual function shall obey the following constraints:
    ...

    1. Let r be the number of characters in the pending sequence not consumed. If r is nonzero then pbase() and pptr() shall be set so that: pptr() - pbase() == r and the r characters starting at pbase() are the associated output stream. In case r is zero (all characters of the pending sequence have been consumed) then either pbase() is set to nullptr, or pbase() and pptr() are both set to the same non-null value.
    2. The function may fail if either appending some character to the associated output stream fails or if it is unable to establish pbase() and pptr() according to the above rules.

    Thus, the function fails if it is unable to actually write to the underlying medium, or unable to restore the invariants. It succeeds if it managed both.