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.
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.