The file streams and string streams do not define stream insertion/extraction operators of their own;
these are either inherited from the base class(es)
or free functions written in terms of std::basic_ostream<>/std::basic_istream<>
It has to do with inheritance. The << operator is the same one as used in any other type of output stream, like cout, or stringstream. Internally the << operator just performs the conversion from the given type into an array of characters, what happens with this array of characters depends on the exact type of your stream.
Basically it allows you to use your overloaded operator for any outputstream type you come across. Say you define your function like this:
ostream& operator<<(ostream&, const Obj&);
You could use your new overloaded operator on file streams, string streams, cout or whatever custom stream you come across. ostream is just a typedef for a basic_ostream<char>.
Since basic_ostream is also the one defining all other versions of operator<<, this still allows for chaining, for as long as you make sure all your operators accept the same type as all other overloads of operator<<. It also allows the code in the standard library to only be defined once, instead of once per type of stream.
The only problem is that you can't differentiate between as easily, although using dynamic_cast you can still check if your instance of the stream is actually a file stream, so this is still possible, but it requires a bit of a workaround.
So if I understood correctly, every stream's operator<< is defined to take and return the base class ostream... and I'm kinda forced to do the same If I want my code to be compliant with all other types