3

i have a problem with function overloading. I will show you with some simple example:

class A {};
class B : public A{};

void somefunction(A&, A&);
void somefunction(B&, B&);

void someotherfunction() {
...
A& a1 = ...
A& a2 = ...

...
}

Both a1 and a2 are instances of B but

somefunction(a1,a2);

calls

void somefunction(A&, A&);

What did i do wrong? I mean polymorphism and overloading are for stuff like that, arent they?

edit: Ok now i know it does not work (thanks for your answers).

Any solution how to do this? Without casting.

edit2: Ok left it as it is, with type casting, since something i would like to have is not possible. Thanks all for your help.

George
  • 3,765
  • 21
  • 25

10 Answers10

9

Cast them statically so that the compiler knows which one to pick:

void somefunction((B&)a1, (B&)a2);

The reason why you are having this problem is with the program design, not the language. Compiler picks which which function is used based on the types that are passed in. C# will behave in exactly the same way (pretty sure Java will too).

It seems to me that you are implementing polymorphism in the wrong place. somefunction really belongs inside class a and should be virtual. Then whenever it's called on the instance of a at runtime the override in the right class will be called.

So, really it should be something like this:

class a {
public:
  virtual somefunction(a& a2) {
    //do stuff
  }
}

class b : public a {
  virtual somefunction(a& a2) {
    b& b2 = (b&)a2;
    //do stuff
  }
}


class c : public b {
  virtual somefunction(a& a2) {
    c& c2 = (c&)a2;
    //do stuff
  }
}

The above solution uses minimal casting inside the virtual function and assumes that the two instance of the same type. This means that b.somefunction(a()) will have undefined behaviour.

A better solution is to rely on C++ RTTI and use dynamic_cast, which will return NULL if the downcast is not possible.

This problem is known as double dispatch problem and is described in the wikipedia article pretty much as you described it. Furthermore, the only solution that wikipedia gives for multiple dispatch is to use dynamic_cast.

EDIT OK, this has been bugging me, here is the solution for full double dispatch between a base class and two subclasses. It aint pretty and uses a bit of C++ trickery like friend classes (for better encapsulation actually, rather than the reverse) and forward declarations.

class b;
class c;
class a {
protected:
    virtual void somefunction(a& a2); //do stuff here 
    virtual void somefunction(b& b2); //delegate to b
    virtual void somefunction(c& c2); //delegate to c
public:
    virtual void doFunc(a& a2) {
        a2.somefunction(*this);
    }
    friend class b;
    friend class c;
};

class b : public a {
protected:
    virtual void somefunction(a& a2); //do stuff here 
    virtual void somefunction(b& b2); //do stuff here
    virtual void somefunction(c& c2); //delegate to c
public:
    virtual void doFunc(a& a2) {
        a2.somefunction(*this);
    }
    friend class a;
};


class c : public b {
protected:
    virtual void somefunction(a& a2); //do stuff here 
    virtual void somefunction(b& b2); //do stuff here
    virtual void somefunction(c& c2); //delegate to c
public:
    virtual void doFunc(a& a2) {
        a2.somefunction(*this);
    }
    friend class a;
    friend class b;

};
//class a
void a::somefunction(a& a2)  {
    printf("Doing a<->a");
}
void a::somefunction(b& b2)  {
    b2.somefunction(*this);
}
void a::somefunction(c& c2)  {
    c2.somefunction(*this);
}
//class b
void b::somefunction(a& a2)  {
    printf("Doing b<->a");
}
void b::somefunction(b& b2)  {
    printf("Doing b<->b");
}
void b::somefunction(c& c2)  {
    c2.somefunction(*this);
}
//class c
void c::somefunction(a& a2)  {
    printf("Doing c<->a");
}
void c::somefunction(b& b2)  {
    printf("Doing c<->b");
}
void c::somefunction(c& c2)  {
    printf("Doing c<->c");
}
Igor Zevaka
  • 74,528
  • 26
  • 112
  • 128
  • As i said, i want to avoid type casting. And what if i had another class C isa A and another somefunction(C&,C&)? (which i actually have, i just wanted to post a simple example) – George Jan 29 '10 at 02:31
  • `somefunction(C&,C&)` will not conflict with `somefunction(B&,B&)`. All the cast is doing is removing ambiguity in which function needs to be used. – Igor Zevaka Jan 29 '10 at 02:40
  • But i have to do some checks to see if its of type B or C to call the function. I want to avoid any checks or type casts. – George Jan 29 '10 at 02:47
  • 1
    Then you should use a more dynamic language and not c++ – Stefan Arentz Jan 29 '10 at 03:39
  • Wow, nice tricks you used there :) Well as i posted here (a few posts below), i used another solution (dispatch table), but because i really liked this post and it answers my question i consider this as the final answer to my problem. Thank you very much! – George Jan 31 '10 at 05:06
3

The function to call is only determined at run-time for virtual methods, based on the type of the this object:

A* a = new B;
a->foo();  //calls B::foo (as long as foo is virtual)

The function to call is not resolved at run-time based on the "real" type of a function's arguments.

A* a = new B;
X* x = new Y;
a->foo(x); //assuming virtual and two overloads, calls B::foo(X*), not B::foo(Y*)

There is no built-in double dispatch mechanism (to select the function to call based on the dynamic types of two objects at the same time), although the pattern can be manually implemented as some posts show.

If you say that you always know that the A& will actually be B& and don't want casts, I conclude that the types will be hard-coded known at compile-time, so you might try "compile-time polymorphism" instead. (In this case A and B don't even need to be related, as long as they have a suitable interface.)

class A {};
class B {};

class C: public A {};

void somefunction(const A&, const A&);
void somefunction(const B&, const B&);

template <class T>
void someotherfunction()
{
    const T& a1 = T();
    const T& a2 = T();
    somefunction(a1, a2);
}

int main()
{
    someotherfunction<A>();
    someotherfunction<B>();

    //combine with inheritance and it will still be
    //possible to call somefunction(A&, A&) since
    //somefunction(C&, C&) is not defined
    someotherfunction<C>();
}

Now a1 and a2 will really be As in one instantiation and Bs in the other case, as far as selecting the overload is concerned. (I added some consts, because otherwise it would be harder to produce something that binds to non-const references.)

visitor
  • 8,564
  • 2
  • 26
  • 15
  • No the types wont be hard coded at compile time. But thanks for your try to help me! – George Jan 29 '10 at 11:18
  • I didn't mean strictly hard-coded, but generated at compile-time with templates. If the types were really only determined at run-time, I don't see how you can so confidently say that your casts will always be correct. - You know better, though, as it might mean very big architectural changes. Just keep in mind that inheritance is not a magic cure for all types of problems (I believe, problems like yours are partly the reason, why the STL was not implemented with runtime polymorphism). – visitor Jan 29 '10 at 11:56
2

As others have already mentioned, the compiler picks the correct overload - its how the language works.

If you are really sure of what type the instances are, you should just cast. If not, one way you can get around manual type-checking at run-time is double dispatch:

struct A;
struct B;

struct Base {
    virtual perform(Base& b) = 0;
    virtual perform(A& a)    = 0;
    virtual perform(B& b)    = 0;
};

struct A : Base {
    virtual perform(Base& b) { b.perform(*this);       }
    virtual perform(A& a)    { someFunction(a, *this); }
    virtual perform(B& b)    { someFunction(b, *this); }
};

struct B : A {
    virtual perform(Base& b) { b.perform(*this);       }
    virtual perform(A& a)    { someFunction(a, *this); }
    virtual perform(B& b)    { someFunction(b, *this); } 
};

// ...
Base& b1 = foo1();
Base& b2 = foo2();
b1.perform(b2);
Georg Fritzsche
  • 97,545
  • 26
  • 194
  • 236
1

what exactly are you trying to do? it looks like you are trying to write a function that does something given two objects, and you want it to do a different thing based on the type of the combination of objects?

remember that even normal polymorphism does "checks" internally.

this is an interesting problem tho,

polymorphism gives you the ability to easily overload the functionality of a function based on the type of ONE object, not two.

what is it EXACTLY that you are trying to do? my best suggestion would be to make it so that each object could perform its own specific stuff separately and then return a common object for common processing:

class Base
{
  virtual SomeComonInterfaceObject DoMySpecialSomething() = 0;
}

void _doSomething(SomeComonInterfaceObject a, SomeComonInterfaceObject b);

void doSomething(Base& o1, Base& o2)
{ 
     _doSomething(o1->DoMySpecialSomething(), o2->DoMySpecialSomething());
}

if that doesn't suit, you probably just have to check the type and do specifics based on that.

note that even normal polymorphism does "checks" if you are worried about performance, any other language would have to too.

the only way you might be able to get around that is by using templates, and it would probably get real ugly.

would be interesting to know what you are trying to do. also, these doSomething functions, is it always the case that their two parameters are the same type? or do they mix and match?

matt
  • 4,042
  • 5
  • 32
  • 50
  • Thats for that, but as you said that doesnt suit me :( No the parameters are not always the same type, they may mix and match as you said. What im trying to do is to make some specific operations only on some combinations of derived classes and "catch" it somehow in the most general function (with &Base, &Base) and do some error handling. – George Jan 29 '10 at 10:45
0

Yes but C++ decided which function to use at compile time, not at runtime. And at compile time the only thing the compiler sees is (A&, A&) - it cannot know that those are actually instances of B.

Stefan Arentz
  • 34,311
  • 8
  • 67
  • 88
  • Well but shouldnt the compiler use late binding for stuff like that? – George Jan 29 '10 at 02:30
  • 'Late Binding' is something used for linking code. Not relevant for this case. – Stefan Arentz Jan 29 '10 at 02:32
  • 1
    @George: Nope. It *could* do this: examine the argument types at runtime, and determine which function to call. But that's not how the language is defined, so it won't. :) – jalf Jan 29 '10 at 03:23
  • Yes it could. I thought that it does (looks logical to me) but i guess there is a good reason not to do so. – George Jan 29 '10 at 04:03
  • It could do it statically in this particular scenario, but in the general case, A& could be anything. You don't want the behavior to depend on the logical possibility/impossibility of accurate static analysis, do you? Or if you think, it could technically always choose overloads at run-time with RTTI, then sorry, that wouldn't be C++ (which is not supposed to be a scripting language). – UncleBens Jan 29 '10 at 18:43
0

You should post more code....what is
A& a1 = ...
A& a2 = ...

Shouldn't you use pointers? If you're storing a1 and a2 as type A then even if they are also B's the A overload gets called. You'd have to dynamic_cast them.

Chris H
  • 6,433
  • 5
  • 33
  • 51
0

In this case compiler will always call somefunction(A&, A&);. Why would it call somefunction(B&, B&);?

fastcodejava
  • 39,895
  • 28
  • 133
  • 186
0

You said in a comment that you're SURE that they are B's.

If that is the case then this is what you want to do.

B a1(); B a2();

If you ever need A's, you can do this (A*)&B. That is an implicit cast and I'm pretty sure it happens at compile time.

Chris H
  • 6,433
  • 5
  • 33
  • 51
0

Your compiler has chosen what it thinks is the most appropriate overload. a1 and a2 are both declared as references to class A, so they fit the overload which takes references to class A "better" than they fit the other one, since that would require some sort of implicit cast to convert them to class B.

Note also that you can't implicitly upcast that way. If you have a pointer or reference to an instance of the base class (A in this case) then it can't be implicitly converted to a derived class, because in general not all instances of the base class are instances of the derived class (all Bs are As, but not all As are Bs).

You will need to declare them as instances of B before calling the function:

B& b1 = ...
B& b2 = ...
somefunction(b1, b2);
Peter
  • 7,216
  • 2
  • 34
  • 46
0

What i will do is to use a dispatch table to get what i want. Instead of 1 dimensional it may be 2 or 3 dimensional (probably 2). Thanks all for trying to help me!

George
  • 3,765
  • 21
  • 25