I'm looking into providing ostream
operators for some math classes (matrix, vector, etc.) A friend has noted that the gcc standard library implementation of the ostream
operator for std::complex
includes the internal use of a string stream to format the output before passing it to the actual ostream
:
/// Insertion operator for complex values.
template<typename _Tp, typename _CharT, class _Traits>
basic_ostream<_CharT, _Traits>&
operator<<(basic_ostream<_CharT, _Traits>& __os, const complex<_Tp>& __x)
{
basic_ostringstream<_CharT, _Traits> __s;
__s.flags(__os.flags());
__s.imbue(__os.getloc());
__s.precision(__os.precision());
__s << '(' << __x.real() << ',' << __x.imag() << ')';
return __os << __s.str();
}
This pattern is visible in boost as well. We're trying to determine if this is a pattern worth following. There have been concerns that it involves including an extra header for the string stream and there are additional heap allocations required within the string stream which could potentially be avoided.
Most reasonably it has been suggested that if the client requires that functionality, then they can create the string stream and do the pre-pass themselves.
Can anyone help me understand why this would be considered good practice and whether I should be adopting it?
Consider what happens if you set an output width on the ostream, then write a std::complex to it - you don't want the width to only affect the first output operation (i.e. the '('
character)
std::complex i(0, 1);
std::cout << std::setw(10) << std::left << i;
This should print "(0,1) "
not "( 0,1)"
By forming the entire output as a single string then writing it out the output honours the field width and other formatting flags set on the stream.
The threading reason quoted in another response won't really work out: the string can still get split on the stream buffer level because these operations aren't atomic when being called from multiple threads.
However, there are two considerations which are relevant:
- For certain outputs you want to temporarily change the format flag settings. For example, you want to make sure that certain string appear using hex notation, for other dec notation and you want to restore the stream to its original state.
- More importantly, the meaning of an output's
width()
is the number of characters the entire formatting string should occupy at least. If you use output operators internally to another output operator you would make the first element to occupy the width rather than the resulting entire string consisting of multiple components. For example, for a complex number the real element would occupy width()
rather than the combination of the real element, the comma, and the imaginary element.
One primary purpose of this pattern is to avoid retaining the original stream's manipulators/flags and resetting them before returning. Boost.IoStateSavers obviates the need for this, so I would say that using said library would be a better practice.