Search code examples
c++booststliostreamboost-iostreams

std::ostream that invokes a callback for each line


I am trying to write a custom std::ostream that invokes a function for every line written to it. That is, I would like the following code to work as documented in comments:

my_output_stream s([] (const std::string& line) { 
    WriteLineToSomeOutput(line); 
});

s << "Hello world"; // do not invoke anything. The line has not ended yet!
s << ", from Alex" << std::endl; // here we invoke WriteLineToSomeOutput("hello world, from Alex")
s << "What's up"; // do not invoke anything. The line has not ended yet.
s << ", doc?!\nHow u doing?\n"; // Now we have two lines. We invoke WriteLineToSomeOutput("What's up, doc?!) and WriteLineToSomeOutput("How u doing?")

Note that the data is not written anywhere and not stored anywhere. The only thing that I need the stream to store is the current line that is being aggregated, until we encounter an end of line.

I did not find any easy way of doing so, even when using the boost.Iostreams library. Can I avoid writing my own line tokenizer here by using some built-in facilities of STL and Boost?

Background

The my_output_stream class will be used to adapt between an external library and a logging library used in my application. The external library requires that I provide an std::ostream. And I want to log each line that is logged by the external library using my application's logging framework.


Solution

  • If I understand correctly, you want to unconditionally flush at end of line, and only at end of line. To do this, you must implement your own streambuf; it could be based on std::stringbuf, but if you're only concerned with output, and not worried about seeking, it's probably just as easy to do it yourself.

    Something like the following should do the trick:

    class LineBufferedOutput : public std::streambuf
    {
        std::vector<char> myBuffer;
    protected:
        int overflow( int ch ) override
        {
            myBuffer.push_back( ch );
            if ( ch == '\n' ) {
                //   whatever you have to do...
            }
            //  return traits::eof() for failure...
        }
    };
    

    I'm not sure what you mean by implementing your own tokenizer; there's no tokenization involved. You do have to look at each character, in order to compare it to '\n', but that's all.

    And you ignore any explicit requests to sync().