38

When I ran the following program

#include <iostream>

int main()
{
   char c = 'a';
   std::cout << c << std::endl;
   std::cout.operator<<(c) << std::endl;

   return 0;
}

I got the output

a
97

Digging further at http://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt, I noticed that std::ostream::operator<<() does not have an overload that has char as the argument type. The function call std::cout.operator<<(a) gets resolved to std::ostream::operator<<(int), which explains the output.

I am assuming that the operator<< function between std::ostream and char is declared elsewhere as:

std::ostream& operator<<(std::ostream& out, char c);

Otherwise, std::cout << a would resolve to std::ostream::operator<<(int).

My question is why is that declared/defined as a non-member function? Are there any known issues that prevent it from being a member function?

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • 1
    Maybe because of the character type template parameter? This function can have the same ("efficient") implementation for any character type by using the locale's `widen`. – dyp Jul 07 '15 at 17:07
  • 1
    Interesting topic. Might be a reason why [what was discussed here](http://stackoverflow.com/questions/31230237/enforcing-unsigned-char-to-be-numerically-outputted-from-function-call) doesn't work. – πάντα ῥεῖ Jul 07 '15 at 17:14
  • 1
    @dyp, that does not seem very convincing. – R Sahu Jul 07 '15 at 17:14
  • 3
    Well, right now for all streams we have an `operator<<` taking a `charT` and an `operator<<` taking a `char`. That's going to be tricky to do as members since `charT` can - and often is - `char`. – T.C. Jul 07 '15 at 17:16
  • The whole design doesn't really convince me :D -- but yes, that raises the question why the other inserters aren't free functions either.. – dyp Jul 07 '15 at 17:17
  • For chars, it is indeed defined as non-member operator function - http://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt2 – Krizz Jul 07 '15 at 17:27
  • @T.C. There's even a specialization `template basic_ostream& operator<<(basic_ostream& out, char c); ` for this case – dyp Jul 07 '15 at 17:28
  • @T.C. I think your explanation makes the most sense. All free functions have this issue. They're `char`-inserters or `char*`-inserters, in various flavours. – dyp Jul 07 '15 at 17:30
  • 3
    http://www.open-std.org/JTC1/SC22/WG21/docs/papers/1996/N0918.pdf – dyp Jul 07 '15 at 17:51
  • http://open-std.org/jtc1/sc22/wg21/docs/papers/1996/N0880.pdf "9.2 Library WG" mentions the issue solved by N0918. So if there's a paper containing the rationale for the solution, it should be somewhere around this time span. – dyp Jul 07 '15 at 18:16

2 Answers2

13

The set of inserters for std::basic_ostream includes partial specializations for inserting char, signed char, unsigned char and such into basic_ostream<char, ...> streams. Note that these specializations are made available for basic_ostream<char, ...> streams only, not for basic_ostream<wchar_t, ...> streams or streams based on any other character type.

If you move these freestanding templates into the main basic_ostream definition, they will become available for all specializations forms of basic_ostream. Apparently, library authors wanted to prevent this from happening.

I don't really know why they wanted to introduce these specializations on top of the more generic

template<class charT, class traits>
basic_ostream<charT,traits>& operator<<(basic_ostream<charT,traits>&,
                                        char);

inserter, but apparently they had their reasons (optimization?).

The same situation exists for C-string inserters. In addition to the more generic inserter

template<class charT, class traits>
basic_ostream<charT,traits>& operator<<(basic_ostream<charT,traits>&,
                                        const char*);

the library specification also declares more specific

template<class traits>
basic_ostream<char,traits>& operator<<(basic_ostream<char,traits>&,
                                       const char*);

and so on.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • 2
    I'm not sure I fully understand your answer: There's a generic `template basic_ostream& operator<<(basic_ostream& out, char c); ` function template that works fine for `wchar_t`. – dyp Jul 07 '15 at 17:29
  • 1
    @ dyp: Yes, but at the same time they felt the need to introduce the dedicated specialization for inserting `char` values into `char` streams. I don't really know why, but the library spec says such specialization exists. – AnT stands with Russia Jul 07 '15 at 17:32
  • I'm not entirely sure if that "specialization" prevents an overload resolution issue. – dyp Jul 07 '15 at 17:33
  • 1
    @AnT, *Apparently, library authors wanted to prevent this from happening.* is most likely the reason. It will be interesting if someone involved in the development of the libraries can throw some light on the subject. – R Sahu Jul 07 '15 at 17:46
6

One reason is following the general C++ advice of preferring non-member non-friend functions to member functions. This is item 23 in Scott Meyer's Effective C++. This is discussed in stackoverflow.

Community
  • 1
  • 1
snow_abstraction
  • 408
  • 6
  • 13
  • 2
    Good to mention this. This rule-of-thumb does not seem to be known by many programmers, leading to god object or object orgy antipatterns... – JHBonarius Feb 15 '17 at 09:50
  • Right, I usually think about it in terms of coupling or with avoiding that everything that one could do with some class becoming overly coupled. I'd rather that enhancements by done via the public interface so I don't have to worry about how so many function could potentially change state the object (affecting class invariants), even if `const` member can help as well. This also makes it easier to partition functions into various more focused headers files. – snow_abstraction Feb 16 '17 at 08:21