问题
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.
回答1:
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>
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.
Live Example
回答2:
Ok , so this is defiantly not the cleanest/shortest solution but here is one way of doing it:
namespace custom
{
struct sep
{
sep(const std::string & s)
:separator(s)
{
}
std::string separator;
};
}
typedef std::basic_ostream<char, std::char_traits<char> > CoutType;
typedef CoutType& (*StandardEndLine)(CoutType&);
class SeparatorWrap
{
public:
SeparatorWrap(std::ostream & _ofs, const custom::sep & s)
: ofs(_ofs)
, separator(s)
{}
template <class W>
SeparatorWrap& operator << (W && w)
{
ofs << separator.separator << w;
return (*this);
}
ostream & operator << (const StandardEndLine &)
{
//writing std::endl will remove the separator
return ofs << std::endl;
}
protected:
std::ostream & ofs;
custom::sep separator;
};
class SeparatorWrapFirst
{
public:
SeparatorWrapFirst(std::ostream & _ofs, const custom::sep & s)
: ofs(_ofs)
, separator(s)
{}
template <class W>
SeparatorWrap operator << (W && w)
{
ofs << w;
return SeparatorWrap(ofs, separator);
}
ostream & operator << (const StandardEndLine &)
{
//writing std::endl will remove the separator
return ofs << std::endl;
}
protected:
std::ostream & ofs;
custom::sep separator;
};
SeparatorWrapFirst operator << (std::ostream & ofs,const custom::sep & s)
{
return SeparatorWrapFirst(ofs, s);
}
int main()
{
std::cout << custom::sep(", ") << 1 << "two" << 3 << std::endl;
}
Here is how it works:
std::cout << custom::sep(", ")
returns a class of type SeparatorWrapFirst
(using the global operator <<
) which is used to write one value without separators the output. This is because if you have one element you don't need to write the separator.
After the first operator <<
from SeparatorWrapFirst
is called, the class SeparatorWrap
is returned and that prints with the separator also. This is for multiple values.
Edit:
So from the comments (@gexicide) it appears that i's possible to put custom manipulator inside std::cout. This could allow you to do something like:
std::cout << custom::sep(", ");
std::cout << 1 << "two" << 3 << std::endl;
Where the first solution from above will not work for this.
来源:https://stackoverflow.com/questions/22840258/how-to-implement-custom-sticky-manipulator-that-automatically-adds-separators