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.
std::streambufclass instead, such as by deriving fromstd::basic_stringbuf(which is whatstd::(i|o)stringstreamuses), overridingsync()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 yourstreambufclass to a standardstd::ostreamobject.std::ios_base::unitbuf(e.g., usingstd::cout << std::unitbuf;). This causes a flush to be called after each output operation. While that would callflush()at the end of the statement, it also calls it at other places. For example,flush()would be called 3 times forstd::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.