10

After looking at the ostream::operator << c++ reference,

I noticed the following declarations:

ostream& operator<< (bool val); ostream& operator<< (short val); ostream& operator<< (unsigned short val); ostream& operator<< (int val); ostream& operator<< (unsigned int val); ostream& operator<< (long val); ostream& operator<< (unsigned long val); ostream& operator<< (float val); ostream& operator<< (double val); ostream& operator<< (long double val); ostream& operator<< (void* val); ostream& operator<< (streambuf* sb ); ostream& operator<< (ostream& (*pf)(ostream&)); ostream& operator<< (ios& (*pf)(ios&)); ostream& operator<< (ios_base& (*pf)(ios_base&)); 

But then I found out that there are also the following declarations:

ostream& operator<< (ostream& os, char c); ostream& operator<< (ostream& os, signed char c); ostream& operator<< (ostream& os, unsigned char c); ostream& operator<< (ostream& os, const char* s); ostream& operator<< (ostream& os, const signed char* s); ostream& operator<< (ostream& os, const unsigned char* s); 

Why are the char/string output operators not member functions?

2

5 Answers 5

6

The first group of operators are members of the stream class.

Most operator overloads, like those in the second group, are not.


As to the why, it is likely just a historical accident. The operators for built in types can be added to the stream classes, and obviously they were (long before C++ was standardized). The standard just documents existing practice here.

Operators for user defined types obviously cannot be added to the stream classes, so they are implemented as free functions.

In retrospect it would have been more consistent to make all the operators free functions, but that would possibly break some old programs.

Sign up to request clarification or add additional context in comments.

14 Comments

Do you know why those standard char overloads weren't made members of the stream class?
@DrewDormann Might be a good question on its own. I think to remember to already having seen problems (SO questions) caused indirectly by this distinction.
@ChristianRau from the comments here, I suspect this question is that question.
Yea, that's the main issue here ;)
@DrewDormann, I have a theory, feel free to comment on my answer if you think it makes no sense
|
2

The others have described the differences, here's my shot at why they are split.

The non-member versions, which take char variants as parameters are specialized depending on type of the target stream's base character type and handled depending on locale, because standard defines specific behavior (o << '1' has different behavior than oo << 33, it has to be handled properly if o is basic_ostream<wchar_t>, etc).

E.g. char writen to basic_ostream<wchar_t> is widened, while if written to basic_ostream<char> it is not (27.6.2.5.4). So actually there are multiple overloads of operator<<, e.g:

basic_ostream<_Elem, _Traits>& operator<<(basic_ostream<_Elem, _Traits>& _Ostr, char _Ch) basic_ostream<char, _Traits>& <<(basic_ostream<char, _Traits>& _Ostr, char _Ch) 

If they were defined as member functions, you wouldn't be able to specialize them like that as you would have to partially specialize the whole class, not just a single member, since standard doesn't allow partial specialization of member functions.

2 Comments

But then again you cannot specialize freestanding function templates (or at least you couldn't at the time those operators were added to the standard, but maybe in C++98 they weren't freestanding at all?). In the end you could customize them as easily (or maybe even easier) if they were member functions. And e.g. widening and such stuff is what specialized char traits and locales are for, no need to actually specialize the stream operators, I think.
Actually, this is not a partial specialization but overloading, right? I have edited the answer. The similar issue is discussed here: gotw.ca/gotw/049.htm
1

There are actually two families of overloader operator<<s, as you have found out.

One family is the overloaded member ostream::operator<< (which gets the ostream reference implicitly as the this pointer) and the other family is the overloaded free function operator<< which gets the ostream reference explicitly as an argument.

When you want to add streaming capability to a class of your own, you add a free function.

5 Comments

Okay, and is there a good reason for why the output operators for char,unsigned,...,const unsigned char* are declared in different location?
@Rouki: One reason: assume you have an instance s of a class that is not an ostream, but has a conversion operator to ostream. If operator<< were just a member of ostream then s << 1 would of course not compile. However, with the free function the compiler can see that there is a function that could match if s were an ostream -- and what do you know? There is a conversion operator it can use to do that! You will find many operators defined as free functions (e.g. see here) for this reason.
The free functions do not forward to member functions - note they have different input types. Subclassing is not a reason for this difference, since a reference-to-derived can convert to a reference-to-base either way. I don't know the actual reason.
@aschepler: True, I had in mind the case where there might be polymorphic behavior. Corrected. Regarding subclassing, I don't quite get what you are referring to? The example above does not have to do with subclassing.
@aschepler, I believe that it is related to specializing them depeding on target character width - I have give more info in my answer
0

The reason for operator<< taking char is not a member function is that there already are functions defined in ostream which write characters to the stream.

basic_ostream& put(Ch c); basic_ostream& write(const Ch* p, streamsize n); 

Consequently these operators are made non-member functions which internally uses these two functions.

Comments

-1

The first series are member functions, which return *this so operator chaining works.

The second series are freestanding functions (which as you say you can add for any class so you can stream them to a std::ostream), which have no this to return and return os instead to make operator chaining work.

Note that both work the same way at a call site.

4 Comments

my understanding of the question is that the point is why are they separated, not what's the difference
Asked both to be honest ;)
@ZdeslavVojkovic the answer is the same, because the questions ask the same thing: the former are part of the class, so already know what std::ostream& to return, the latter need an additional argument.
Maybe I wasn't clear: the confusing part is why not having them all as members or all as non-members.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.