1. What Hiding is
I am afraid there is something wrong with your little example, and I suspect that your problems lies in Hiding.
Let's illustrate what Hiding is first:
struct Base { void foo(int) { std::cout << "Base" << std::endl; } };
struct Derived: Base { void foo(float) { std::cout << "Derived" << std::endl; } };
int main(int, char* argv[])
{
Derived d;
int integer = 1;
float floating = 2;
d.foo(floating); // outputs "Derived" as expected
d.foo(integer); // outputs "Derived" too UhOh ?
}
The problem is referred to as Hiding, it's an issue with name resolution at compilation time. The issue is that before applying the overloading rules to select the "right" method, the compiler needs first to compile the set of methods to consider. To do so it's going to look at the most specialized scope, and then spread outwards one scope at a time until it finds the name it's looking for. Unfortunately (for efficiency reasons I guess), it stops as soon as it has found functions with the name.
So what happens here is:
- Look for methods named
foo in Derived: { void Derived::foo(float) }
And stop... therefore when trying to resolve foo for an int argument, it selects the only method it has seen so far, which might be a bit surprising.
You are correct that you can trump this with the using keyword, which brings names from another scope so that the compiler consider them. If I add using Base::foo; in Derived definition, the compiler will do:
- Look for methods named
foo in Derived: { void Derived::foo(float) } + using
- Add methods named
foo in Base: { void Derived::foo(float), void Base::foo(int) }
And therefore you obtain what you wished.
2. How to redefine a method
Now that you know what Hiding is, and how to circumvent it, I'd like to take the opportunity to address your example flaws.
You should not redefine the methods like this:
struct Base { void foo(int) const { std::cout << "Base" << std::endl; } };
struct Derived: Base { void foo(int) const { std::cout << "Derived" << std::endl; } };
The problem here is that it's merely Hiding and that can be surprising.
void fooize(const Base& b) { b.foo(); }
int main(int argc, char* argv[])
{
Derived d;
d.foo(); // output "Derived"
fooize(d); // output "Base"
}
If you wish to redefine you need the virtual keyword. And then you'll correct your classes this way:
struct Base
{
virtual ~Base() {} // Polymorphism means virtual destructor
virtual foo(int) const { std::cout << "Base" << std::endl; }
};
struct Derived: Base
{
virtual ~Derived() {} // Not necessary, but sweet reminder
virtual foo(int) const; // virtual not necessary, but a sweet reminder again :)
};
And then it will work as expected.
3. Polymorphism cook book
- You should not redefine a method that is not
virtual
- Polymorphism thus means
virtual Destructor (because it will be redefined anyway)
- Inheritance introduces Polymorphism, use Composition if you don't want it
It is unfortunate (for Composition) that there is no "delegation" concept in C++, but we have to deal with the cards we've been given.
A final note: if the class has a lot of methods... it may be it could benefit from a redesign (see std::string for an example of what not to do).