In some languages like Ruby, there is fancy syntax for creating formatted output as a "one-liner" in part because of how commonly this is needed in Ruby applications.
It's also commonly needed in some C++ applications. Here's some example code:
if (num_doggies > num_biscuits) {
std::ostringstream ss;
ss << "There are " << num_doggies" << " puppy dogs and only " << num_biscuits << " biscuits!";
log_warn(ss.str());
this->negotiate_biscuit_reduction();
}
Some open source C++98 projects have a workaround that looks like this:
struct string_formatter {
std::ostringstream ss;
template <typename T>
string_formatter & operator << (const T & t) {
ss << t;
return *this;
}
operator std::string() const {
return ss.str();
}
};
So that they can write something like this:
if (num_doggies > num_biscuits) {
log_warn(string_formatter{} << "There are " << num_doggies" << " puppy dogs and only " << num_biscuits << " biscuits!");
this->negotiate_biscuit_reduction();
}
turning three lines into one line.
However, the string_formatter
class looks fairly evil from a code-review standpoint,
mainly because of this implicit conversion. Implicit conversions are usually considered
pretty evil, and they are more evil when they are converting to common or primitive types
like int
or std::string
.
In C++11 we can change it slightly:
operator std::string() && {
return ss.str();
}
Now the conversion will only trigger when the string_formatter
is an r-value reference.
So it's less likely to trigger, but it's also more complicated. Is it actually less evil?
Debatable.
Also worth mentioning: others have gone to even more elaborate hacks to get this to work
without a string_formatter
shim: https://codereview.stackexchange.com/questions/6094/a-version-of-operator-that-returns-ostringstream-instead-of-ostream
No implicit conversions here, but we are overloading operators on standard library types which is usually a no-no.
Let's back up a bit. Why can't I compromise and do this? It's at least a little more compact:
if (num_doggies > num_biscuits) {
std::ostringstream ss;
log_warn((ss << "There are " << num_doggies" << " puppy dogs and only " << num_biscuits << " biscuits!").str());
this->negotiate_biscuit_reduction();
}
It turns out that this doesn't compile, because std::ostringstream::operator <<
returns std::ostream &
and not
std::ostringstream &
. By the time all the <<
have resolved, we only have an ostream &
so we can't use .str()
.
My question is, why is that the case?
Why doesn't std::ostringstream::operator <<
return std::ostringstream &
? Is it:
- Undesirable for technical or safety reasons
- Adding unnecessary difficulty for standard library implementors
- Legacy -- it was always this way and changing it now might break code. (However, exactly what code would be broken by this? Since
std::ostringstream &
can be converted tostd::ostream &
by a standard conversion because of inheritance relationship?) - Obscurity -- no one cares!
Note: I'm well aware of different ways to design string_formatter
and logger classes. I'm specifically interested in whether there are design reasons in the standard library that make it much better for std::ostringstream
to return std::ostream &
.
It's not clear from your post whether you would care what the
operato<<
functions return if you can simplify your code that deals with logging the warning messages.Here's one way you can simplify the code that logs warning messages.
In your case, you can simply use: