Why is operator<< function between std::ostream and char a non-member function?
Asked Answered
G

2

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?

Glenglencoe answered 7/7, 2015 at 16:54 Comment(10)
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.Agio
Interesting topic. Might be a reason why what was discussed here doesn't work.Cascio
@dyp, that does not seem very convincing.Glenglencoe
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.Pronation
The whole design doesn't really convince me :D -- but yes, that raises the question why the other inserters aren't free functions either..Agio
For chars, it is indeed defined as non-member operator function - en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt2Robertoroberts
@Pronation There's even a specialization template<class traits> basic_ostream<char,traits>& operator<<(basic_ostream<char,traits>& out, char c); for this caseAgio
@Pronation I think your explanation makes the most sense. All free functions have this issue. They're char-inserters or char*-inserters, in various flavours.Agio
open-std.org/JTC1/SC22/WG21/docs/papers/1996/N0918.pdfAgio
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.Agio
D
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.

Damnatory answered 7/7, 2015 at 17:27 Comment(4)
I'm not sure I fully understand your answer: There's a generic template<class charT, class traits> basic_ostream<charT,traits>& operator<<(basic_ostream<charT,traits>& out, char c); function template that works fine for wchar_t.Agio
@ 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.Damnatory
I'm not entirely sure if that "specialization" prevents an overload resolution issue.Agio
@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.Glenglencoe
G
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.

Gere answered 15/2, 2017 at 9:27 Comment(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...Fernferna
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.Gere

© 2022 - 2024 — McMap. All rights reserved.