A fun fact about the ‘+’ operator is that it can be applied a lot of different types. We can use it on integers, on doubles, even on strings. For each of these, it does some kind of addition. This is method overloading. There are multiple different version of ‘+’ with different signatures, so we can have an abstract notion of addition that works across different types. The great thing is, we can do this in our own code!
What is Overloading?
Overloading a method is when you have more than one method with the same name but different argument types in a class. Something like
class Greeter {
public:
std::string say_hello() {
return "Hello";
}
std::string say_hello(std::string name) {
return "Hello " + name;
}
}
Given a Greeter
object, a call to say_hello
with no arguments will call the first method and a call to say_hello
with a string
argument will call the second method.
So we can make as many methods name say_hello as we want in this class with different parameters. However, we cannot have an overload that differs only in return type, like this
class Greeter {
public:
std::string say_hello() {
return "Hello";
}
// Compiler error!
bool say_hello() {
return true;
}
}
With this code, if you had a Greeter
object, and you call say_hello
the compiler wouldn’t know which method you are referring to. This is because a call to the say_hello method, might be part of a larger expression, with more method calls, like so:
f(g(greeter.say_hello()) + 5) * g(a)
when this happens, the compiler works recursively upwards, from the deepest method call to the topmost, resolving the types as it goes. So the compiler uses the return type of a method to resolve the type of the parameters of the method at the next level up. So it always needs to know what the return types are.
Don’t hide your wonderful code!
Suppose now we have a base class and a subclass, like this
class BasicGreeter {
public:
std::string say_hello() {
return "Hello";
}
}
class PoliteGreeter : BasicGreeter {
}
If you have a PoliteGreeter
object, you can call the say_hello
method from it’s base class. But, suppose we added a say_hello
method to PoliteGreeter itself, like so
class BasicGreeter {
public:
std::string say_hello() {
return "Hello";
}
}
class PoliteGreeter : BasicGreeter {
public:
std::string say_hello() {
return "Hello there good sir";
}
}
This will compile happily. But, because both say_hello
methods have the same signature, the say_hello
method in PoliteGreeter
is hiding the say_hello
method in BasicGreeter
. If we call say_hello
from a PoliteGreeter
object we will get the PoliteGreeter
version of that method, not the BasicGreeter
version. However, we can still access the BasicGreeter
version of the say_hello
method by using the base class name directly, like so
PoliteGreeter greeter;
greeter.BasicGreeter::say_Hello(); // This is the sub class say_hello method!
What happens if we try to overload a base class method in a subclass?
class BasicGreeter {
public:
std::string say_hello() {
return "Hello";
}
}
class PoliteGreeter : BasicGreeter {
public:
std::string say_hello(std::string name) {
return "Hello there " + name;
}
}
Bad news, even though they have different signatures, the base class method is still hidden! This is quite counter-intuitive to me. In fact, the compiler could make both methods visible in the PoliteGreeter
class. But, the C++ standard does not allow it, for confusing and obscure reasons, that I do not understand.
The good news is that we can still use the previous trick to access the subclass method.
PoliteGreeter greeter;
greeter.say_Hello(); // Compiler error! The BasicGreeter say_hello method is hidden!
greeter.BasicGreeter::say_Hello();
We can also add a using
statement to the PoliteGreeter
class itself, that will make the say_hello
method in BasicGreeter
visible from a PoliteGreeter
object.
class PoliteGreeter : BasicGreeter {
public:
using BasicGreeter::say_hello;
std::string say_hello(std::string name) {
return "Hello there " + name;
}
}
If we want to call the say_hello
method from BasicGreeter
inside PoliteGreeter without making it visible outside of the class we can reference the subclass like this:
class PoliteGreeter : BasicGreeter {
public:
std::string say_hello(std::string name) {
return BasicGreeter::say_hello() + name;
}
}