Search code examples
c++templatesoperator-overloadingstdstream-operators

Overload of operator<< with basic_ostream


Why the typical header of stream manipulation with a user-defined class C is typically like this:

std::ostream& operator<<(std::ostream& os, const C& c);
std::istream& operator>>(std::istream& is, C&);

and not like this:

template <class CharT, class Traits> 
std::basic_ostream<CharT, Traits>& operator<<(
        std::basic_ostream<CharT, Traits>& os
        const C& c);

template <class CharT, class Traits> 
std::basic_istream<CharT, Traits>& operator>>(
        std::basic_istream<CharT, Traits>& is
        C& c);

My question is why the usual overloading of stream operators is done with std::ostream, which is a typedef for char of std::basic_ostream, and why it is not done directly with std::basic_ostream ?

For example:

class C
{
    ...
};

std::ostream& operator<<(std::ostream& os, const C& c)
{
    ...
}

int main()
{
    C c;
    std::wofstream myFile("myFile.txt");
    myFile << c; //Impossible
}

The operator<< written here limit us to use only stream object that are specialized for char (std::ostream, std::ostringstream, ...). So if using std::ostream is more limiting than std::basic_ostream, why std::basic_ostream is never mentioned when talking about stream operators overloading?


Solution

  • In practice, there are two different character types used:

    1. Windows uses wchar_t as a result of promises made by the Unicode people a long time ago (that Unicode would only use 16 bits and that each character would consist of just one unit) and which have been broken since.
    2. Everybody else uses char which is now mostly considered to be a byte in a UTF-8 encoding (obviously, not universally).

    In retrospect, the introduction of wchar_t (and even more so of char16_t and char32_t) was ill-advised and the world would be better off if only char would be used. As a result, those not bothered by Windows don't care about a wchar_t version of the I/O operations and Windows in general seems to punt on IOStreams in general (the MSVC++ implementation is known to be slow and there is zero intention to do anything about it).

    Another reason is that writing templatized I/O operators is seen to be adding complexity to an already complex system. Few people seem to understand IOStreams and among these few even fewer are interested in supporting multiple character types.

    One aspect of the perceived complexity with templatizing I/O operators is the assumption that the implementation needs to go into the header which is, of course, not true given that there are essentially just two character types IOStreams are instantiated with (char and wchar_t): although IOStreams can be instantiated with other character types, I'm pretty sure that hardly anybody actually does so. Although I know what it takes, it would probably still take me at least a day to define all the necessary facets. Thus, the template definitions could be defined in suitable translation units and instantiated there. Alternatively, instead of defining the operators are templates they could be fully specialized.

    Independent on how the templated operators are defined, it is generally more work. If done naively (i.e., directly using e.g., std::ctype<cT>) the result will be slow and when doing it properly (i.e., caching results from std::ctype<cT>) it will be quite involved.

    Taking it all together: why bother?

    If I had to write to std::wostream or read from std::wistream I'd actually create a filtering stream buffer which just translates the the characters written/read using a suitable std::codecvt<...> facet (or even just using std::ctype<wchar_t>'s widen() or narrow()). It wouldn't deal with proper internationalization of strings but the std::locale facilities aren't really up to proper internationalization anyway (you'd need something like ICU for that).