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.
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:
std::streambuf
.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.