0

I want to extend the usage of std::cout to use my own console/cout wrapper class.

Ideally I would have 2 ostreams, one for regular printing and one that appends a new line.

std::ostream Write;
Write << "Hello, I am " << 99 << " years old.";

prints Hello, I am 99 years old.

std::ostream WriteLine;
WriteLine << "Hello, I am " << 99 << " years old.";

prints Hello, I am 99 years old.\n (an actual new line, not just it escaped)

I would then like to extend this to have error streams (Error and ErrorLine, for example) which prefix "ERROR: " before the message and prints in a different color.

I know I have to create my own streams to add in this functionality, and I followed C++ cout with prefix for prefixing std::cout which was almost what I wanted but not quite. I couldn't figure out how to add a new line to the end of the stream, and the prefix was unreliable, especially when I would do more than a single print statement.

I should also mention I don't want to use overloaded operators to achieve this effect, because I want to be able to daisy-chain things on.

What didn't work

If I did WriteLine >> "First"; then WriteLine << "Second"; I would get weird results like SecondFirst\n or Second\nFirst. My ideal output would be First\nSecond\n. I think it is due to not closing/flushing/resetting the stream properly, but nothing I tried got it to work reliably.

I could get it to work for a single statement, but as soon as I added another print statement, the things I tried to print would switch order, the post/pre fix wouldn't get added in the correct spot, or I would end up with garbage.

I don't care about wchars, because we will always only need a single byte for a single char. Also we will only be working on Windows 10.

This is what I have so far:

Console.h

#include <windows.h>
#include <iostream>
#include <sstream>
#include <string>

class Console {
    using Writer = std::ostream;
    Console() {}
    static const char newline = '\n';

    class error_stream: public std::streambuf {
    public:
        error_stream(std::streambuf* s) : sbuf(s) {}
        ~error_stream() { overflow('\0'); }
    private:
        typedef std::basic_string<char_type> string;

        int_type overflow(int_type c) {

            if(traits_type::eq_int_type(traits_type::eof(), c))
                return traits_type::not_eof(c);
            switch(c) {
            case '\n':
            case '\r':
            {
                SetColor(ConsoleColor::Red);
                prefix = "ERROR: ";
                buffer += c;
                if(buffer.size() > 1)
                    sbuf->sputn(prefix.c_str(), prefix.size());
                int_type rc = sbuf->sputn(buffer.c_str(), buffer.size());
                buffer.clear();
                SetColor(ConsoleColor::White);

                return rc;
            }
            default:
                buffer += c;
                return c;
            }
        }

        std::string prefix;
        std::streambuf* sbuf;
        string buffer;
    };


    class write_line_stream: public std::streambuf {
    public:
        write_line_stream(std::streambuf* s) : sbuf(s) {}
        ~write_line_stream() { overflow('\0'); }
    private:
        typedef std::basic_string<char_type> string;

        int_type overflow(int_type c) {

            if(traits_type::eq_int_type(traits_type::eof(), c))
                return traits_type::not_eof(c);
            switch(c) {
            case '\n':
            case '\r':
            {
                buffer += c;
                int_type rc = sbuf->sputn(buffer.c_str(), buffer.size());
                sbuf->sputn(&newline, 1);
                buffer.clear();

                return rc;
            }
            default:
                buffer += c;
                return c;
            }
        }

        std::streambuf* sbuf;
        string buffer;
    };

    static output_stream outputStream;
    static error_stream errorStream;
    static write_line_stream writeLineStream;

    public:
    static void Setup();

    static Writer Write;
    static Writer WriteLine;

    static Writer Err;
};

Console.cpp

#include "Console.h"

Console::Writer Console::Write(nullptr);
Console::Writer Console::WriteLine(nullptr);

Console::Writer Console::Err(nullptr);

Console::error_stream Console::errorStream(std::cout.rdbuf());
Console::write_line_stream Console::writeLineStream(std::cout.rdbuf());

void Console::Setup() {
    Write.rdbuf(std::cout.rdbuf());
    Err.rdbuf(&errorStream);
    WriteLine.rdbuf(&writeLineStream);
}

Main.cpp

int main() {
    Console::Setup();
    Console::Write << "First" << "Second";
    Console::WriteLine << "Third";
    Console::WriteLine << "Fourth";
    Console::Write << "Fifth";
    Console::Error  << "Sixth";
    Console::ErrorLine  << "Seventh";
    Console::WriteLine << "Eighth";
}

Which should give an output of

FirstSecondThird
Fourth
FifthERROR: SixthERROR: Seventh
Eighth
Press any key to continue...

Any help and/or suggestions are appreciated.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
anon
  • 59
  • 6
  • The output was always incorrect. I could get it to work for a single statement, but as soon as I added another print statement, the things I tried to print would switch order, the post/pre fix wouldn't get added in the correct spot, or I would end up with garbage. – anon Jan 30 '18 at 21:08
  • [Edit] your question to clarify and add additional information please. –  Jan 30 '18 at 21:09
  • 1
    Did you read the 6th paragraph? – anon Jan 30 '18 at 21:10
  • Yes I've read it, and before your edit what kept _sticky_ was: _"but nothing I tried got it to work reliably."_ –  Jan 30 '18 at 21:14
  • The things I changed were implementing different variations of the classes from the linked question, rearranging the order of adding to the buffer, attempting to flush it various ways, and overriding different functions from `streambuf`. I've been trying to find a solution for a few days so I can't remember exactly everything I have tried. – anon Jan 30 '18 at 21:18
  • 3
    "*I know I have to create my own streams to add in this functionality*" - actually no. It would be easier to write a custom `std::streambuf` class instead, such as by deriving from [`std::basic_stringbuf`](http://en.cppreference.com/w/cpp/io/basic_stringbuf) (which is what `std::(i|o)stringstream` uses), overriding [`sync()`](http://en.cppreference.com/w/cpp/io/basic_streambuf/pubsync) to prefix/append whatever you want when flushing printed output to whatever device you want (console, disk, etc). You can then attach an instance of your `streambuf` class to a standard `std::ostream` object. – Remy Lebeau Jan 30 '18 at 21:28
  • @RemyLebeau I made a class deriving from `std::stringbuf` which overrides `sync()` like so: `int sync() { std::cout << prefix << str() << suffix << std::flush; str(""); return std::cout ? 0 : -1; }`. Again, it only works for a single line. If I attach it to an `std::ostream` with a prefix of `*` and suffix of `!`, and run `myStream << "Hello";` it prints `*Hello!`, which is what I want. However, if I run `myStream << "Hello";` `myStream << "Goodbye";`, I get `*HelloGoodbye!` opposed to `*Hello!*Goodbye!`. `*HelloGoodbye!` would be my expected output for `myStream << "Hello" << "Goodbye";` – anon Jan 30 '18 at 22:11
  • @anon: `myStream << "Hello"; myStream << "Goodbye";` and `myStream << "Hello" << "Goodbye";` are **identical**. Individual calls to `<<` are accumulated in the `streambuf` until it is flushed, which DOES NOT occur on a statement's `;` like you are thinking. A flush occurs when `'\n'` is written, `flush()` is called directly (`myStream.flush()` or `myStream << std::flush`), or the `streambuf` overflows. To get the kind of output you are looking for, you need to flush `myStream` after writing a complete message to it, or else create a separate `ostream`/`streambuf` pair for each message. – Remy Lebeau Jan 30 '18 at 22:58
  • Is there a way to handle the flushing behind the scenes inside `myStream`? I know the flush triggers `sync()` but I want to internally flush after a statement has been completed. Where would the flush go if I didn't want to write `myStream << "Hello" << std::flush;` ? I tried placing it in `sync` and `overflow` but it didn't change the output. – anon Jan 30 '18 at 23:04
  • @RemyLebeau: note that IOStreams do *not* flush receiving a newline character - unless they are made to do so. The default stream deliberated do *not* do that: it would require to look at the individual characters and, potentially, to get the timing right to process individual characters in `overflow()`. While that may be reasonable for some custom application it is a really bad idea for any substantial amount of writing as it slows down processing dramatically! You generally want to set up a buffer in a stream buffer which doesn't overflow too often. – Dietmar Kühl Jan 31 '18 at 00:20
  • 1
    @anon: you can set `std::ios_base::unitbuf` (e.g., using `std::cout << std::unitbuf;`). This causes a flush to be called after each output operation. While that would call `flush()` at the end of the statement, it also calls it at other places. For example, `flush()` would be called 3 times for `std::cout << std::unitbuf << "hello " << 1 << " world\n";`. The alternative is to use a temporary created at the start of the statement which flushes upon destruction. However, this entire temporary faffing about is somewhat annoying. – Dietmar Kühl Jan 31 '18 at 00:23
  • @DietmarKühl: "*note that IOStreams do not flush receiving a newline character - unless they are made to do so*" - in general, that is true. But streams like `std::cout` and `std::cerr` may be line-buffered, thus flushing when `'\n'` is written. Also, `std::cout` is usually `tie()`d to `std:cin`, so `std::cout` flushes whenever `std::cin` is read from. But [things are usually more complicated than they seem](https://stackoverflow.com/questions/42430701/). – Remy Lebeau Jan 31 '18 at 01:38

1 Answers1

6

There are multiple concerns here which do require different approaches. Some of the description also seems that the actual desire isn't quite clear. The most problematic requirement is that a newline needs to be inserted apparently at the end of a statement. That's certainly doable but effectively does require a temporary being around.

Before going there I want to point out that most other languages offering a print-line(....) construct delineate what is going onto a line using a function call. There is no doubt where the newline goes. If C++ I/O would be created now I'd be quite certain that it would be based on a variadic (not vararg) function template. This way print something at the end of the expression is trivial. Using a suitable manipulator at the end of the line (although probably not std::endl but maybe a custom nl) would be an easy approach.

The basics of adding a newline at the end of expression would be using a destructor of a suitable temporary object to add it. The straight forward way would be something like this:

#include <iostream>

class newline_writer
    : public std::ostream {
    bool need_newline = true;
public:
    newline_writer(std::streambuf* sbuf)
        : std::ios(sbuf), std::ostream(sbuf) {
    }
    newline_writer(newline_writer&& other)
        : newline_writer(other.rdbuf()) {
        other.need_newline = false;
    }
    ~newline_writer() { this->need_newline && *this << '\n'; }
};

newline_writer writeline() {
    return newline_writer(std::cout.rdbuf());
}

int main() {
    writeline() << "hello, " << "world";
}

This works reasonable nice. The notation in the question doesn't use a function call, though. So, instead of writing

writeline() << "hello";

it seems necessary to write

writeline << "hello";

instead and still add a newline. This complicates matters a bit: essentially, writeline now needs to be an object which somehow causes another object to jump into existence upon use so the latter can do its work in the destructor. Using a conversion won't work. However, overloading an output operator to return a suitable object does work, e.g.:

class writeliner {
    std::streambuf* sbuf;
public:
    writeliner(std::streambuf* sbuf): sbuf(sbuf) {}
    template <typename T>
    newline_writer operator<< (T&& value) {
        newline_writer rc(sbuf);
        rc << std::forward<T>(value);
        return rc;
    }
    newline_writer operator<< (std::ostream& (*manip)(std::ostream&)) {
        newline_writer rc(sbuf);
        rc << manip;
        return rc;
    }
} writeline(std::cout.rdbuf());

int main() {
    writeline << "hello" << "world";
    writeline << std::endl;
}

The primary purpose of the overloaded shift operators is to create a suitable temporary object. They don't try to mess with the content of the character stream. Personally, I'd rather have the extra parenthesis than using this somewhat messy approach but it does work. What is kind of important is that the operator is also overloaded for manipulators, e.g., to allow the second statement with std::endl. Without the overload the type of endl can't be deduce.

The next bit is writing the prefix and mixing multiple streams. The important bit here is to realize that you'd want to one of two things:

  1. Immediately write the characters to a common buffer. The buffer is most like just another stream buffer, e.g., the destination std::streambuf.
  2. If the character should be buffered locally in separate stream buffers, the corresponding streams need be flushed in a timely manner, e.g., after each insertion (by setting the std::ios_base::unitbuf bit) or, latest, at the end of the expression, e.g., using an auxiliary class similar to the newline_writer.

Passing through the characters immediately is fairly straight forward. The only slight complication is to know when to write a prefix: upon the first non-newline, non-carriage-return return after a newline or a carriage return (other definitions are possibly and should be easily adaptable). The important aspect is that stream buffer doesn't really buffer but actually passes through the character to the underlying [shared] stream buffer:

class prefixbuf
    : public std::streambuf {
    std::string     prefix;
    bool            need_prefix = true;
    std::streambuf* sbuf;
    int overflow(int c) {
        if (c == std::char_traits<char>::eof()) {
            return std::char_traits<char>::not_eof(c);
        }
        switch (c) {
        case '\n':
        case '\r':
            need_prefix = true;
            break;
        default:
            if (need_prefix) {
                this->sbuf->sputn(this->prefix.c_str(), this->prefix.size());
                need_prefix = false;
            }
        }
        return this->sbuf->sputc(c);
    }
    int sync() {
        return this->sbuf->pubsync();
    }
public:
    prefixbuf(std::string prefix, std::streambuf* sbuf)
        : prefix(std::move(prefix)), sbuf(sbuf) {
    }
};

The remaining business is to set up the relevant objects in the Console namespace. However, doing so is rather straight forward:

namespace Console {
    prefixbuf    errorPrefix("ERROR", std::cout.rdbuf());
    std::ostream Write(std::cout.rdbuf());
    writeliner   WriteLine(std::cout.rdbuf());
    std::ostream Error(&errorPrefix);
    writeliner   ErrorLine(&errorPrefix);
}

I except that the approach adding the newline creates a custom type I think that matches the original goes. I don't think the temporary object can be avoided to automatically create a newline at the end of a statement.

All that said, I think you should use C++ idioms and not try to replicate some other language in C++. The way to choose whether a line end in newline or not in C++ is to write, well, a newline where one should appear potentially by way of a suitable manipulator.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 1
    Thank you for taking the time to write your thought out and insightful answer. I have definitely learned a lot from reading through your post and enjoyed a different perspective. I will keep this in mind for future tasks. I also appreciate the explanations of the logic as they enabled me to fully understand what was written and why. Thanks for expanding my knowledge of streams and buffers. I even learned something new about manipulators! I now understand why my question may have seemed a bit silly but I think a valuable lesson can still be learned. – anon Feb 01 '18 at 06:19