0

I currently have the following structure

class A

class B : public A

class C : public A

I have virtual methods defined in A and B and C are overriding them. The methods are of the sort

bool C::CheckCollision(shared_ptr<B> box);

bool B::CheckCollision(shared_ptr<C> triangle);

I also have a vector of shared_ptr<A> in which I store all the game objects. The problem is that i cannot do the following

for (int i = 0; i < objects.size(); i++)
{
    for (int j=i; j < objects.size(); j++
    {
        objects[i]->CheckCollision(objects[j]);
    }

}

I get an error saying that the argument list does not match the overloaded function. Makes sense as I am trying to pass shared_ptr<A> where i am expecting shared_ptr<B> or shared_ptr<C>, but how do I go around this issue ? Is there a another way to do it ?

Christophe
  • 68,716
  • 7
  • 72
  • 138
Martin Spasov
  • 315
  • 3
  • 7
  • 23
  • 2
    You cannot go around this. What if `CheckCollision()` uses something that's available only in `B` or `C` and you'll pass a `std::shared_ptr` that will point to `A` object? You need to rethink your design. – Fureeish Mar 03 '19 at 12:22
  • makes sense, something else that comes to mind is that I can use an enum to store the type info and do a virtual `GetType` function. I know that you are not aware of the whole situation, but does that sound bad ? – Martin Spasov Mar 03 '19 at 12:26
  • To me, `enum` (preferably `enum class`) that holds a type smells too much `C`, but sometimes it's the right tool. If you truly want polymorphism, you need to stick `virtual` *somewhere*. Do note that it is possible to do a type check using `dynamic_cast`, but it's rarely a desired solution (not only it is slow, but it adds a lot of noise to the code). – Fureeish Mar 03 '19 at 12:31
  • 2
    I would think that `CheckCollision` would belong on `A` and then it would call virtual functions - like maybe `BoundingBox()` - to get the info it needs to run the collision detection algorithm. – mhhollomon Mar 03 '19 at 12:38
  • You have `virtual CheckCollision(shared_ptr box)` and `virtual CheckCollision(shared_ptr box)`, declared in your `A` class? Because if so then the design is definitely broken. `A` should not know something about `B` and `C`. If you don't have `virtual CheckCollision(shared_ptr box)` and `virtual CheckCollision(shared_ptr box)` in `A`, then you don't have a function you override. – t.niese Mar 03 '19 at 12:40
  • Not related to your problem. But if the `CheckCollision` function does not claim ownership of the object you pass to it (if it uses the passed object only in the that function itself) then the argument should be a raw pointer or reference and not a smart pointer. A smarte pointer as parameter indicates that the ownership is claimed or transferred. So you would write `CheckCollision(A *obj)` and call it that way `objects[i].CheckCollision(objects[j].get());`. – t.niese Mar 03 '19 at 12:41
  • @mhhollomon thing is that the checks are different for the different types and I am not sure how to put them under one unified API – Martin Spasov Mar 03 '19 at 12:50
  • @t.niese - yep, they are forward declared and are present in A. Seems like I need to review the design. Also thanks for the second suggestion – Martin Spasov Mar 03 '19 at 12:51
  • @MartinSpasov indeed, this is a promising way to solve it. The easiest would be to add a "bounce back" to select the right overload. In the additional demo (added to my initial answer), I've showed this using references to the object itself (which knows its type). But a dynamic_cast could also be considered. – Christophe Mar 03 '19 at 13:51

1 Answers1

5

Let's make it work with virtual functions and shared pointer to base class

First of all, you can perfectly make polymorphism work, using shared pointers to the base. Here a small snippet to show you how you could do it:

class A {
public: 
    virtual void show() { cout<<"A"<<endl; } 
    virtual void collide(shared_ptr<A> a) { cout<<"collide A with "; a->show();  } 
    virtual ~A() {}
};

class B : public A {
public:
    void show() override { cout<<"B"<<endl; } 
    void collide(shared_ptr<A> a) override { cout<<"collide B with "; a->show();  } 
};

class C : public A {
public:
    void show() override { cout<<"C"<<endl; } 
    void collide(shared_ptr<A> a) override { cout<<"collide C with "; a->show();  } 
};

Your double loop would then look like:

vector<shared_ptr<A>> objects; 
objects.push_back (make_shared<A>());   // populate for the sake of demo
objects.push_back (make_shared<B>()); 
objects.push_back (make_shared<C>()); 

for (int i = 0; i < objects.size(); i++)
{
    objects[i]->show(); 
    for (int j=i; j < objects.size(); j++)
    {
        objects[i]->collide(objects[j]);   // note that you have to use -> not .
    }
}

Now, you see, in order to master the combinations, I used an override that knows the real type of its own object but doesn't know anything specific about the real type of the partner object. So to work out which kind of pairing it is, I need to invoke a polymorphic function of the partner object.

Online demo

The more general approach to your problem is double dispatch

This small proof of concept is to show a simple example. It's ideal when the problem can be decomposed in each of the partner object doing one part of the problem. But things are not always as simple, so you could find more elaborate techniques by googling for double dispatch. Fortunately the example of the collision is quite common.

Here another demo that uses a combination of overriding and overloading. I think this is the kind of things you try to achieve but solves it through one level of indirection more. It is inspired by the visitor pattern: The polymorphic collision function of an object is invoked with a shared pointer to the base class of the partner object. But the implementation of this function immediately calls a polymorphic function of the partner object with as argument a reference to itself (i.e. the knowledge of the real type of the argument, allows the compiler to select the right overload). This kind of "bounce-back" approach (by the way, it's a kind of inverted visitor) unfortunately requires the base class to know all its potential derived class, which is far from ideal. But it allows to offer a different behavior for every possible combination.

Another approach to double dispatch is to use a dispatch table. THis works by managing a kind of virtual table but with two types, and having some lookup for invoking the right function for the right combination.

Christophe
  • 68,716
  • 7
  • 72
  • 138