Search code examples
c++polymorphism

c++ polymorphic method defined in derived class matches type that should match to the parent


I'm experimenting with this code

class Base
{
public:
virtual void foo(int) {
// void foo(int) {
    cout << "int" << endl;
}
};

class Derived : public Base
{
public:
void foo(double) {
    cout << "double" << endl;
}
};

int main()
{
    Base* p = new Derived;
    p->foo(2.1);
    
    Derived d;
    d.foo(2);  // why isn't this double?
    
    return 0;
}

It's also available in online editor here https://onlinegdb.com/s8NwhfG_Yy

I'm getting

int
double

I don't understand why d.foo(2) calls the double version. Since Derived inherits the int method and has its own double method, wouldn't polymorphism dictate that 2 be matched to the parent? Thanks.


Solution

  • Polymorphism as in calling virtual methods is orthogonal to symbol and overload resolution. The former happens run-time, the rest at compile-time.

    object->foo() always resolves the symbol at compile-time - member variable with overloaded operator() or a method. virtual only delays selecting "the body" of the method. The signature is always fixed, including the return value of course. Otherwise the type system would break. This is one of the reasons why there cannot be virtual function templates.

    What you are actually experiencing is name hiding and overload resolution.

    For Base* p; p->foo(2.1), the list of possible symbol candidates is only Base::foo(int). The compiler cannot know that p points to Derived (in general) because the choice must be done at compile time. Since int is implicitly convertible to double, foo(int) is chosen. Because the method is virtual, if Derived were to provide its own foo(int) override, it would have been called instead of Base::foo(int) at run-time.

    For Derived*d; d->foo(2), the compiler first looks for symbol foo in Derived. Since there is foo(double) which is valid as foo(2), it is chosen. If there was no valid candidate, only then the compiler would look into base classes. If there was Derived::foo(int), possibly virtual, the compiler would choose this instead because it is a better match.

    You can disable the name hiding by writing

    class Derived: public Base{
        using Base::foo;
    };
    

    It injects all Base::foo methods into Derived scope. After this, Base::foo(int) (now really Derived::foo(int)) is chosen as the better match.