Home > database >  c polymorphic method defined in derived class matches type that should match to the parent
c polymorphic method defined in derived class matches type that should match to the parent

Time:10-23

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.

CodePudding user response:

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.

CodePudding user response:

Read Overload resolution. The answer is in Details:

If any candidate function is a member function (static or non-static), but not a constructor, it is treated as if it has an extra parameter (implicit object parameter) which represents the object for which they are called and appears before the first of the actual parameters.

The selecting between the methods void foo(int) and void foo(double) is happening like between the functions void foo(Base, int) and void foo(Derived, double). The second function while calling d.foo(2) is ranked higher.

If you add using Base::foo; in Derived, the resolution will be performed between three functions void foo(Base, int), void foo(Derived, int) and void foo(Derived, double), and int will be printed.

  • Related