This is basically a copy from the example given in Item 21. Overriding Virtual Functions
in Herb Sutter's book Exceptional C++
.
#include <iostream>
#include <complex>
using namespace std;
class Base
{
public:
virtual void f(int);
virtual void f(double);
virtual ~Base() {};
};
void Base::f(int) { cout << "Base::f(int)" << endl; }
void Base::f( double ) { cout << "Base::f(double)" << endl; }
class Derived: public Base {
public:
void f(complex<double>);
};
void Derived::f(complex<double>) { cout << "Derived::f(complex)" << endl; }
int main()
{
Base* pb = new Derived;
pb->f(1.0);
delete pb;
}
The code prints Base::f(double)
and I have no problems with that. But I couldn't understand the explanation given by the author on the top of page 122 (emphasis is mine):
Interestingly, even though the Base* pb is pointing to a Derived object, this calls Base::f( double ), because overload resolution is done on the static type (here Base), not the dynamic type (here Derived).
My understanding is that the call pb->f(1.0)
is a virtual call and Base::f(double)
is the final overrider for f(double)
in Derived
. What does that have to do with function overloading?
The reason this example is interesting is because, if
pb
were aDerived*
instead of aBase*
, or if the compiler could somehow use the dynamic type rather than the static type for performing overload resolution, it would match the call topb->f(1.0)
tovoid Derived::f(complex<double>)
(complex<double>
can be implicitly constructed fromdouble
). This is because the presence of a function namedf
in a derived class effectively hides any base class overloads with the same name, even if their argument lists are different. But since the static type ofpb
is actuallyBase*
, this doesn't happen.In this example, despite the repeated occurrence of
virtual
, there is no method overriding at all; the methodf
in the derived class overrides neither of the ones in the base class, because the argument types do not match. Given this circumstance, there is no way the call ofpb->f
could ever invoke the (unique) methodDerived::f
. Overload resolution/name lookup (which only considers methods of the static type ofpb->f
) must choose between the two methods declared asBase::f
, and in the example it will choose the one with argument typedouble
. (At runtime this might end up calling an override, if one is defined in a different derived class thanDerived
, and if the example is modified so thatpb
could possibly point to an object of such another derived class.)A separate issue is that the methods named
f
inBase
andDerived
would not be simultaneously considered for overload resolution iff
is called from an expression of (static) typeDerived
either, this time because the methods in the base class are hidden by the declaration off
inDerived
, so they are not available for such calls. I think this hiding can be avoided by declaringusing Base::f;
,which "lifts" the methodsBase::f
intoDerived
as if they were also declared there (but I admit not knowing the details of this mechanism; I suppose the lifts would then be overrides of the virtual base method with the same argument type, but it makes little difference because the lifts refer to the implementation in the base class anyway.)The delicate part here is that virtual methods are a mechanism to dispatch the function call, while overloading is a feature that affects the resolution of the call.
That is, for any call the compiler needs to figure out which method should be called (resolve it); afterwards, and in a logically distinct operation, it needs to generate code that calls the correct implementation of that method (dispatch it).
From the definitions of
Base
andDerived
given above we can easily reason that iff(double)
is called on aBase*
then the call should be dispatched to any derived override (if applicable) in preference to the base implementation. But answering that is answering a question totally different thanAs Sutter explains, the spec says that when resolving the call the compiler will look at the methods named
f
declared on the static type pointed to bypb
; in this case, the static type isBase*
so overloads (not overrides!) declared onDerived
will not be considered at all. However, if the method that the call resolves to isvirtual
then the possible implementation provided onDerived
will be used as expected.