2

One of the common lessons in C++ is how to overload the insertion operator (<<) when we create our own type. We are told to create a global function named operator<< that has two params, an ostream ref and our type and returns an ostream ref.

std::ostream& operator<<(std::ostream& os, const MyType& mt) {
  ...
}

Now this makes sense when you learn that operators can be called explicitly as functions. For example, the pair of statements are functionally equivalent:

MyType mt;

std::cout << mt;
operator<<(std::cout, mt);

What's interesting though is how insertion works for built-in types. Let's consider strings, ints and even std::endl (which until recently I assumed was a type)

std::cout << "Hello world";
operator<<(std::cout, "Hello world"); // fine

std::cout << 5;
operator<<(std::cout, 5); // ERROR!

std::cout << std::endl;
operator<<(std::cout, std::endl); // ERROR!

There is a global operator<< defined for strings (char arrays) but not for ints or std::endl. If you want to explicitly insert those types, you have to do something different (which incidentally won't work for strings).

std::cout << "Hello world";
std::cout.operator<<("Hello world"); // ERROR!

std::cout << 5;
std::cout.operator<<(5); // fine

std::cout << std::endl;
std::cout.operator<<(std::endl); // fine
std::endl(std::cout); // also equivalent

Int handling is defined as a member function on an ostream and std::endl is actually a function that takes advantage of a function template on ostream.

So my question is how does C++ compilation end up selecting the right function when converting the syntactic sugar for insertion operators? When a developer writes the following, what goes on under the hood to correctly lookup and map to the appropriate function addresses?

std::cout << "Id: " << 5 << std::endl;

Ryan Jarvis
  • 375
  • 3
  • 13
  • 5
    Terminology: "override the insertion operator (<<)" isn't correct; you **overload** the insertion operator. When there are multiple functions with the same name and different argument lists defined in the same scope, that's overloading. When you have a virtual function in a base class and the derived class has a function with the same name and the sam argument list, that's overriding. – Pete Becker Mar 05 '20 at 20:51
  • 2
    There is an entire section in the standard for [overload resolution](https://timsong-cpp.github.io/cppwp/n3337/over.match). – R Sahu Mar 05 '20 at 21:01
  • The quirk that you're seeing, about built-in types vs. others, comes about because the template `std::basic_ostream` (`std::ostream` is a specialization of that template) defines a handful of **members** that overload the `<<` operator for various built-in types. That's why you can say `std::cout.operator<<(3)` but you can't say `std::cout.operator<<("abc")`. – Pete Becker Mar 05 '20 at 21:02
  • https://en.cppreference.com/w/cpp/language/overload_resolution#Call_to_an_overloaded_operator – M.M Mar 05 '20 at 21:36

1 Answers1

3

When using cout << ... (or any other ostream), the compiler looks for BOTH a member method named operator<<() in the stream class, and a non-member function named operator<<() taking the stream class in the 1st parameter, and if there are multiple matches then overload resolution figures out which match is the best one to call based on the other value being passed to the operator.

See operator overloading on cppreference.com for more details, particularly the section on "Overloaded operators".

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • So would you say then that it is just a decision imposed in the C++ standard that a) insertion is an overloaded operator b) overloaded operators build up candidate functions from both member and non-member functions if one of the operands has members and c) insertion is allowed to have non-member function overloads? – Ryan Jarvis Mar 05 '20 at 21:27
  • another quirk to bear in mind is that name lookup for the infix operator syntax is different to name lookup for the function call syntax (the former skips other member functions of the current class, if occurring inside a member function) – M.M Mar 05 '20 at 21:37