Over doing it with methods in C++

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;
    }
}