c++positionostream

Is the C++ ostream at the start of a line?


In C++, how do I detect if my std::ostream os is at the start of a line, in other words (I think) the most recent thing written to os is either os<<'\n' or os<<std::endl(), or else nothing has yet been written to os?

At first glance, this sounds unnecessary, because I can just keep track of the state myself. But a common scenario is the following, where keeping track would involve altering every os<<thing statement which could possibly be called from the try block, in a very unnatural way.

try {
  do_something_which_writes_to(std::cout);
}
catch(const My_error&error) {
  print_a_newline_if_necessary(std::cout);
  std::cout<<error<<"\n";
}

(In reality, of course, we want to write error to std::cerr but that usually gets mixed in with std::cout unless one of them is redirected, so we still want to terminate the std::cout line before printing to std::cerr. I have deliberately simplified the example to avoid this distraction.)

You might imagine that os.tellp() would be the answer, but tellp() seems to work only on std::ofstream. For me at least, std::cout.tellp() always returns -1, indicating that it is not supported.


Solution

  • At least as I'm reading things, what you really want isn't the ability to get the position in the current line. Rather, what you really want is to be able to print something that is guaranteed to be at the start of a line--the current line if the immediately previous character was a newline (and, I'd guess also if it was a carriage return), but otherwise print a newline, then whatever follows.

    Here's some code to do that:

    #include <iostream>
    
    class linebuf : public std::streambuf
    {
        std::streambuf* sbuf;
        bool            need_newline;
    
        int sync() {
            return sbuf->pubsync();
        }
        int overflow(int c) {
            switch (c) { 
                case '\r':
                case '\n': need_newline = false;
                    break;
                case '\v':
                    if (need_newline) { 
                        need_newline = false;
                        return sbuf->sputc('\n');
                    }
                    return c;
                default:
                    need_newline = true;
                    break;
            }
            return sbuf->sputc(c);
        }
    public:
        linebuf(std::streambuf* sbuf)
            : sbuf(sbuf)
            , need_newline(true)
        {}
        std::streambuf *buf() const { return sbuf; }
        ~linebuf() { sync(); }
    };
    
    class linestream : public std::ostream {
        linebuf buf;
        std::ostream &os;
    public:
        linestream(std::ostream& out)
            : buf(out.rdbuf())
            , std::ios(&buf)
            , std::ostream(&buf)
            , os(out)
        {
            out.rdbuf(&buf);
        }
        ~linestream() { os.rdbuf(buf.buf()); }
    };
    
    void do_stuff() { 
        std::cout << "\vMore output\v";
    }
    
    int main() {
        { 
           linestream temp(std::cout);
    
            std::cout << "\noutput\n";
            std::cout << "\voutput";
            do_stuff();
            std::cout << "\voutput\n";
            std::cout << "\voutput\v";
        }
        std::cout << "\voutput\v";
    }
    

    Since it's almost never used otherwise, I've hijacked the vertical tab ('\v') to signify the special behavior.

    To use it, you simply create a temporary object of type linestream (sorry, I'm too tired to think of a good name right now), passing it an ostream object that will get the new behavior when a \v gets written to it. When that temporary object goes out of scope, the stream will be restored to its original behavior (I doubt anybody uses \v often enough to care, but who knows maybe somebody care--it's mostly just a side effect of cleaning up after itself anyway).

    In any case, the special behavior remains in place when do_stuff is called, so it's not just local to the function where the local linestream object is created, or anything like that--once it's created, the special behavior remains in effect until it's destroyed.

    One other point though: when/if you mix output from cout and cerr, this won't help much. In particular, neither will be at all aware of the other's state. You'd probably much need some hooks into the output terminal (or something on that order) to be able to deal with that, since output redirection is normally handled by the OS, so inside the program there's no way to even guess whether data written to cout and cerr are going to the same place or not.