3

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?

  • It's hard to follow you, please clarify your question... Do you mean to ask why using `std::ostream` is more commonly used than `std::basic_ostream`? – tnull Nov 02 '15 at 12:32
  • 1
    Mainly because mentioning it introduces irrelevant complications that are of no interest to the discussion. You're expected to be able to generalise from specifics as needed. – molbdnilo Nov 02 '15 at 12:57
  • 1
    If you made your function generic then you would have to handle converting to all of the different character types in the one function. IMHO opinion this is easier to do when you specify the stream type exactly. – NathanOliver Nov 02 '15 at 13:08
  • @NathanOliver For built-in type the converting is already done, and you can use std::basic_ios::do_widen if you want to write extra characters. I don't think it is really annoying, and this is way more generic, isn't it? – Théo Verhelst Nov 02 '15 at 13:25
  • @ThéoVerhelst what about going the other way? Your class holds wide data and someone want to use a `ofstream` with it? – NathanOliver Nov 02 '15 at 13:28
  • @NathanOliver Effectively, my question should be limited to classes that do not hold things like std::string, std::wstring or other specialized objects. – Théo Verhelst Nov 02 '15 at 13:33

1 Answers1

5

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).

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Great response, it makes sense. Just another little question: how is done streaming and printing to standard output with windows, if IOStream are slow and not used? – Théo Verhelst Nov 02 '15 at 13:49
  • Even on Windows you can use `std::cout` and it has to write to the standard output. It is just that most Windows API use `wchar_t`. You can also use `std::wcout` as the output operators defined by the C++ standard are supported for `std::basic_ostream<...>`. It is still slow, though. Aside from using IOStreams there are other APIs which can write to standard output, too. They are not defined by the C++ standard and are not portable, though. – Dietmar Kühl Nov 02 '15 at 13:52