Please consider the following code:
class Abase{};
class A1:public Abase{};
class A2:public A1{};
//etc
class Bbase{
public:
virtual void f(Abase* a);
virtual void f(A1* a);
virtual void f(A2* a);
};
class B1:public Bbase{
public:
void f(A1* a);
};
class B2:public Bbase{
public:
void f(A2* a);
};
int main(){
A1* a1=new A1();
A2* a2=new A2();
Bbase* b1=new B1();
Bbase* b2=new B2();
b1->f(a1); // calls B1::f(A1*), ok
b2->f(a2); // calls B2::f(A2*), ok
b2->f(a1); // calls Bbase::f(A1*), ok
b1->f(a2); // calls Bbase::f(A2*), no- want B1::f(A1*)!
}
I'm interested to know why C++ chooses to resolve the function call on the last line by upcasting the this
pointer of the object to the base class, rather than upcasting the argument of f()
? Is there any way that I can get the behaviour I want?
The choice of which version of f
to call is made by looking at the compile-time type of the parameter. The run-time type isn't considered for this name resolution. Since b1
is of type Bbase*
, all of Bbase
's members are considered; the one that takes an A2*
is the best match, so that's the one that gets called.
"...chooses to resolve the function call on the last line by upcasting the this pointer of the object to the base class...". What are you talking about? In all of your calls, the object pointer type is Bbase *
and the functions the calls resolve to belong to either Bbase
or its descendants. The compiler never does any upcasting in order to resolve your calls. In fact, the first two calls require downcasting in order to call the proper overrider, since the overrider belongs to the class located further down in the hierarchy. As for the last two calls - they are dispatched into the Bbase
class through a pointer of Bbase *
type. The types match exactly, no casting of any kind takes place.
As for the overload resolution... Overload resolution is a compile time process, which is based on the static types of the arguments and the ranks of possible conversions. You supplied an argument of A2 *
type. The f(A2 *)
candidate matched your argument precisely. The f(A1 *)
candidate requires a extra conversion from A2 *
to A1 *
. The candidate that matches exactly is considered a better one, so it wins the overload resolution. Simple.
b1->f(static_cast<A1*>(a2));
This should force the compiler to use the overload method with parameter of type A1.
It's called name hiding. Every f you declare in one derived class shadows every possible f in any of its base classes.
Use a cast to the base class to get the desired behaviour.
When you override a virtual function, you don't override overloaded functions with the same name. They are different functions (and have different entries in the vtable).
Your overloads in Bbase for Abase and A2 are hidden in B1.
Maybe you can work around that problem like this:
class Bbase{
public:
inline void f(Abase* a) { f_(a); }
inline void f(A1* a) { f_(a); }
inline void f(A2* a) { f_(a); }
protected:
virtual void f_(Abase* a);
virtual void f_(A1* a);
virtual void f_(A2* a);
};
class B1:public Bbase{
protected:
void f_(A1* a);
};
class B2:public Bbase{
protected:
void f_(A2* a);
};
or with a template in Bbase:
class Bbase{
public:
template<class myA>
inline void f(myA* a) { f_(a); }
protected:
virtual void f_(Abase* a);
virtual void f_(A1* a);
virtual void f_(A2* a);
};