The print function in Python automatically separates its arguments with a customisable separator. Is there any way to emulate this behavior in C++ by using stream manipulators?
That is, the following C++ code:
std::cout << custom::sep(", ") << 1 << "two" << 3 << std::endl;
Should work similar to the following Python code:
print(1, "two", 3, sep=", ")
The desired output would be:
1, two, 3
How would I go about implementing custom::sep
? It seems a bit more tricky than your standard custom manipulator, because it cannot just change the next item on the stream, like here or here. It should be sticky until the next custom::sep
or std::endl
. Furthermore, it cannot just work on numbers or certain types, like here. It should work with any streamable type.
The issue with the solution you posted is that it relies on customizing the way integers get formatted using facets. Unfortunately, I do not think there is a corresponding facility which would work for arbitrary types.
There is. You can use utilize the underlying buffer of the stream to get what you want. The buffer is where the character sequence is eventually gathered for maintenance. The following code makes a stream buffer that holds a reference to the object whose character sequence you wish to use. We set the std::ios_base::unitbuf
format flag so that the stream is flushed on each output operation (so we can add the separator to the end).
By extension, it also allows you to uninstall the separator and makes sure that no memory is leaked in the process:
#include <iostream>
#include <string>
namespace custom
{
struct sep_impl
{
sep_impl(std::string const& separator);
std::string separator;
};
sep_impl sep(std::string const& str)
{
return sep_impl(str);
}
std::ostream& nosep(std::ostream& os);
}
int separatorEnabled()
{ static int idx = std::ios_base::xalloc(); return idx; }
int getSeparator() { static int idx = std::ios_base::xalloc(); return idx; }
struct custom_separator : std::streambuf
{
public:
custom_separator(std::ostream& _stream) : stream(_stream)
{ }
int_type overflow(int_type c)
{
return stream.rdbuf()->sputc(c);
}
int sync()
{
if (stream.iword(separatorEnabled()))
{
void*& p = stream.pword(getSeparator());
stream << *static_cast<std::string*>(p);
return 0;
}
return stream.rdbuf()->pubsync();
}
private:
std::ostream& stream;
};
void cleanup(std::ios_base::event evt, std::ios_base& str, int idx)
{
if (str.iword(separatorEnabled()) && evt == std::ios_base::erase_event)
{
void*& p = str.pword(idx);
delete static_cast<std::string*>(p);
str.iword(separatorEnabled()) = false;
}
}
std::ostream& set_separator(std::ostream& os, const custom::sep_impl& manip)
{
if (!os.bad())
{
os.pword(getSeparator()) = new std::string(manip.separator);
os.register_callback(cleanup, getSeparator());
}
return os;
}
std::ostream& operator<<(std::ostream& os, const custom::sep_impl& manip)
{
std::ostream* p = os.tie();
if (p && !p->iword(separatorEnabled()))
{
set_separator(*p, manip);
p->iword(separatorEnabled()) = true;
}
return os << std::unitbuf;
}
namespace custom
{
sep_impl::sep_impl(std::string const& _sep) : separator(_sep) { }
std::ostream& nosep(std::ostream& os)
{
cleanup(std::ios_base::erase_event, *os.tie(), getSeparator());
os.tie(nullptr);
return os << std::nounitbuf;
}
void install_separator(std::ostream& o1, std::ostream& o2)
{
static custom_separator csep(o2);
o1.rdbuf(&csep);
o1.tie(&o2);
}
}
int main()
{
std::ostream os(nullptr);
custom::install_separator(os, std::cout);
os << custom::sep(", ") << 4 << 2 << custom::nosep;
}
I'm sure there is also room for improvement, so if anyone has any suggestions they are very much appreciated.