Overloaded virtual function call resolution
Asked Answered
S

5

7

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?

Stableman answered 23/6, 2010 at 15:38 Comment(0)
M
10

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.

Mm answered 23/6, 2010 at 15:59 Comment(1)
Thanks - as I understand it the point is that the resolution of which virtual f() to call happens at compile time, based on the argument supplied to f(). So on the last line, the compiler has already decided that f(A2*) will be called. The version of f(A2*) called then depends on the runtime type being pointed to. Here, since class B1 doesn't overide f(A2*), the base class version is called.Stableman
R
2

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

Rossetti answered 23/6, 2010 at 16:30 Comment(1)
Sorry- I used the term 'upcasting' in the wrong way. What I meant was why is the object treated as a BBase here. The answer (as you say) is that overload resolution happens at compile time, and at compile time the object is a BBase, so the compiler chooses f(A2*).Stableman
N
1
b1->f(static_cast<A1*>(a2));

This should force the compiler to use the overload method with parameter of type A1.

Nabal answered 23/6, 2010 at 15:43 Comment(8)
a dynamic_cast is more appropriate, I think.Inapposite
@Jason: A static_cast to a base class is fine.Agnes
I think a static cast is OK, as we a) know that the cast is correct and b) it is an upcast, which is safe.Nabal
you can static_cast to derived classes too, in fact.Microscopic
Yes you can, however a dynamic_cast will give you some meaningful information if it fails.Nabal
@Jason, @inflagranti: When the cast is an upcast, dynamic_cast cannot give any "meaningful information". In fact, there's absolutely no difference between dynamic_cast and static_cast when it comes to upcasts. Upcast cannot fail. Its validity is known at compile time. It either doesn't compile, or works successfully. There's absolutely no point in using dynamic_cast for upcasts. Moreover, it would be misleading. dynamic_cast should be reserved for downcasts only.Rossetti
I agree. I was refering to the case where you static cast a derived class (i.e. downcast) - which is exactly where the runtime checks of dynamic cast make sense.Nabal
I agree that this will generate the right call in the example. However, in the real problem I'm considering, I have a list of BBase pointers, and I want to call f() through each. Although adding a cast fixes the fourth case, it will break the second case!Stableman
M
0

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

Microscopic answered 23/6, 2010 at 15:45 Comment(1)
No, the problem here is the other way around. It would be name-hiding if you'd call through a pointer to a more derived class which hides method(s) from the base class.Agnes
P
0

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);  
};
Piercy answered 23/6, 2010 at 16:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.