Hi, I've been working with a tree-like structure and encountered the following issue that I'll try to formulate in a simplified setting. Consider the following 2 classes:
class B;
//--------------------------
class A
{
public:
void F(A& a){
F_(a); // Want to call either F_(A&) or F_(B&)
};
virtualvoid F_(A&)=0;
virtualvoid F_(B&)=0;
};
//-------------------------------
class B : public A
{
public:
virtualvoid F_(A& a){
std::cout << "a" << std::endl;
};
virtualvoid F_(B& b){
std::cout << "b" << std::endl;
};
};
//--------------------------------------
int main(array<System::String ^> ^args)
{
B b1, b2;
A &b=b1;
b.F(b2); // I want b.F to call F_(B&) !!!
return 0;
}
I thought that b.F(b2) would eventually result in calling F_(B&), however when I run the program in VC++ 2005, it outputs "a". That indicates that no overloading takes place. So what do I do wrong?
If I replace b.F(b2) with b.F_(b2), then everything works as expected and the program outputs "b". I don't like this solution though, as I was hoping to keep the interface of A clean by declaring the functions F_ private.
And finally, in the code above, the child class B appears in the interface of the parent class A: virtualvoid F_(B&)=0. Is there any way to avoid that and yet achieve the desired function overloading?
I do not understand why are you calling b.F(b2); and not b.F_(b2);? The second one does print out b, as expected.
When c++ calls b.F(b2) it "typecasts" (or "downgrades" if you wish) your b2 object to object of type A. Then naturally it has no choice but to call F_(A&).
My comment is not an answer, just tried to identify in more detail where the problem lies.
#include <iostream>
//--------------------------
class A
{
public:
staticvoid F(A& a)
{
a.F_();
};
private:
virtualvoid F_()=0;
};
//-------------------------------
class B : public A
{
private:
virtualvoid F_()
{
std::cout << "b" << std::endl;
};
};
//-------------------------------
class C : public A
{
private:
virtualvoid F_()
{
std::cout << "c" << std::endl;
};
};
//--------------------------------------
int main()
{
B b1;
C c1;
A::F(b1);
A::F(c1);
return 0;
}
One option is to use dynamic_cast to determine the type at runtime:
1 2 3 4 5 6 7 8 9 10 11 12 13
void F(A& a)
{
try
{
B& b = dynamic_cast<B&>(a);
F_(b); // dynamic cast successful, call B version
}
catch(...)
{
F_(a); // dynamic cast failed, call A version
}
};
//..
Or if you want to avoid the try/catch reference version of dynamic_cast, you can use the pointer version:
1 2 3 4 5 6
void F(A& a)
{
B* b = dynamic_cast<B*>(&a);
if(b) F_(*b);
else F_(a);
}
Or if you want to avoid dynamic cast completely (since it can be computational expensive), you can build in your own kind of simplistic runtime check for this kind of thing. This would be easier if you have several of these kinds of classes and not just 2 (trying to dynamic_cast to each of them would be a killer):
Thank you folks for all the replies, I've learned a lot from them.
However, I am still not sure how to implement the polymorphism based on the function argument type while keeping the parent class A to be completely unaware of its children classes.
Ideally, I'd like to have something like
It looks like what you want to do is not directly possible in C++. I think you have a fundamental misunderstanding of polymorphism and/or inheritance.
Polymorphism does not translate from one function type to another function type. For example void F_(A&); and void F_(B&); are two completely different function types despite them having the same name and parameter count. C::F_(B&) does not polymorph A::F_(A&) because they're two different functions.
The only way to have a function in class A call a function in a child class without A being aware of the children classes is for A to share a virtual function with the child.
ie:
1 2 3 4 5 6 7 8 9 10
class C : public A
{
virtualvoid F_(B& b); // the only way for A to call this function...
};
class A
{
virtualvoid F_(B& b); // is for it to have that same function
virtualvoid F_(A& a); // this does nothing at all to help you here
};
Of course you could say this requires A to be aware of children classes. Which brings me back to my original point: not directly possible in C++.
The best you can do is have children classes polymorph the function that "forks" the type (in this example, that would be A::F(A&)). This would require runtime checks of the type (so either dynamic_cast or a method similar to what I talked about in my previous post) to figure out which function to fork to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
class A
{
virtualvoid F(A& a);
};
class C : publicclass A
{
virtualvoid F(A& a)
{
switch(a.getType())
{
case type_a: F_(a); break;
case type_b: F_(static_cast<B&>(a)); break;
case type_c: F_(static_cast<C&>(a)); break;
//.. etc
}
}
};
Thank you Disch, that clarifies a lot of things for me. I am trying to adapt to C++ as I was a bit spoiled by programming in typeless languages in the last few years (although I haven't used function overloading either).
Using downcasting is very tempting indeed, even though this can become a maintenance issue later on. I guess, I'd have to completely redesign the code in order to get rid of the downcast altogether.
Thanks again,
Eugene