OK so I will admit that the classic double dispatch demo was a really long demo.
It was almost 20 minutes of us just building the double dispatch functionality for both expression printer
and expression evaluator.
And the one thing that I did which might strike you as a little bit weird is I did this check for bracers
and I inserted the braces only when they were needed.
Now why did I do this.
Well because I wanted to demonstrate something that's a bit more complicated than what we're dealing
with here.
So at the moment what's happening is we have a visitor pattern where each of the visit methods just
takes a single argument and when you take a single argument it's very easy to do the double dispatch
because it's always obvious what overload you should be calling you take you basically call accept and
accept pass as this point here and that way you know which overload a visit to actually call.
Now what I've been doing here with checking the subtraction is on the right hand side is I'm highlighting
a situation where you might need a visit method which actually takes two arguments so let's suppose
that the rules for the left and right hand side of a admissions expression or subtraction expression
was so specific that you would have to create overloads for two arguments and instead of calling the
left except and right except individually you would actually take the left and the right and you would
call some sort of combine except on it.
How would that work.
Well in actual fact what I'm going to do is I'm going to try and show you a situation where the multiple
dispatch actually can be implemented but we're not going to do it in the context of a visitor because
in the context of a visitor it can get really ugly and in actual fact this is left as a bit of an exercise
because what we're going to take a look out is essentially something that's called the multimethods.
So the idea is that you perform the kind of dispatch that we've been doing but you perform it on let's
say two arguments instead of one argument.
So I'm going to actually take a classic example that has been around for ages and that is the idea of
collisions in space so you have three different kind of constructs.
You have a planet an asteroid and a spaceship and we can also implement other more complicated stuff.
But the idea is as follows You have these three objects and depending on which object collides with
which other objects you have different effects.
For example if a space ship collides with a planet Well the spaceship can land on the planet.
If an asteroid hits a planet then it burns up in the atmosphere.
If an asteroid hits a spaceship then it destroys the spaceship obviously so we want to implement all
of the scenarios.
We want to do it in the context of object oriented programming.
So let's actually try to somehow implement all of this.
So here is the basic idea let's suppose you have some game object and that can be a spaceship or whatever
and then what you want to do is you want to implement a function called collide which collides to game
objects.
So here is game object first and game object second.
And you actually want them to collide.
So what is a game object exactly.
Well let's just define it in terms of simple things.
So a game object is going to be basically a base class.
The actual game objects implement.
Now what do we want from the game object.
Well in this particular case what we want to do is we want to have a member function also called collide
which would simply reuse the global function that I've defined here.
So I will define a virtual void collide.
So we want to collide with some other game object when to collide with this particular object.
And all we going to do here is we're going to call the global collide passing reference to ourselves
as well as reference to the other object.
OK so why I'm doing this will become clear in a moment but there is another thing that I want to add
here and that will be a virtual function which will return a type index and I don't think we have a
header for this yet.
So here is a virtual function returning a type index so I'm going to go into headers and I'm going to
add the appropriate header here.
Type index.
There we go.
OK so having said this up what we can now do is we can make those game objects so I can make a structure
called Planet which is going to be a game object and we need to pause here.
So it's not going to inherit from game objects specifically because what I'm going to do is I'm going
to have an in-between class called game object implementation and the idea of this class is to see RTP.
Now remember when I talked about at the very early stages of the implementation of the visitor pattern
I talk that something that we did in one way could also have been done using CRT.
Well guess what we're going to see RTP here so I'm going to have a template class with type name T.
And it's going to be called game object Imphal and it's going to inherit from game object.
So here's the idea what we want to do is we want to implement type index obviously but the only way
we can do this is by creating a type ID of some type and that type is going to be T.
So what I'm going to do here and let's see if I can get C line to generate the override for me is I'm
going to return a type ID of the type T here.
OK so now what's going to happen is we're going to be using CRT.
So instead of inheriting from game object we'll be inheriting from game object in pool passing in ourselves
as the type argument.
So that's that's the planet I'm just going to replicate this a few times.
So we're going to have a.
We're going to have asteroid or US going to have a spaceship.
OK.
So what I going to do now is I want to define certain functions which get cold on different collisions.
So for example a I'm going to have a function for a space ship colliding with a planet so this function
is just going to see out spaceship lands on planets like Embree.
There we go.
OK.
And I'm going to have another one which is going to be well let's see asteroid planet.
So asteroid lands on the planet.
And in this case the asteroid burns up.
So I'm going to say asteroid burns up in the atmosphere hope I spelt out fear correctly duplicate this
once again.
So let's have one where the asteroid hits a ship.
So an asteroid hits a spaceship and unfortunately everyone dies.
That's how things are.
Space ship.
Well actually
asteroid hits and destroys spaceship.
There we go.
So this is our own implementation what we can now do is we're going to find this rather unpleasant looking
map of the different possible outcomes as these objects collide.
And the way we can do this is by essentially mapping a pair of type indices to a function pointer.
So here is a map which takes a pair of type indices so pair type index type index and it maps it to
just a void returning function which takes no arguments like so.
All right so these are going to be our outcomes.
OK.
And what we can do is we can just get those type ideas and we can map them to the appropriate functions.
So for example I can say that the time by the off space ship together with the type ID of planet is
going to essentially map to the function space ship.
Now let me just paste in a few more of these definitions.
So we're going to have just think a couple more of these.
OK so these are some definitions of the kind of outcomes and now what we can do is we can finally implement
this global Collider function.
So generate a definition for it.
I'm going to move that definition down.
It's actually cut this from here and move it down here.
So the idea is that when two objects collide what you do is you actually look for them in this map that
we made.
So we get the iterator to the pair So we look for the pair of first and second types.
So we say outcome's top find and we create a pair in the actual fact with first time and second type
remember type is what we got in the impled via the CRT p.
So we don't get anything.
So if it is equal to outcomes dog and we also look for it in the reverse order because remember when
they collide they could colliding any kind of order first to second no second to first so we say it
equals outcomes they'll find actually going to copy this just copy this and replace first with second
and second with us second here and first here.
OK so now if we really don't have it at this point then we're going to see out that objects pass each
other harmlessly.
Otherwise what we're going to do is we're going to return it second.
So it second refers of course to the function pointer that's the second part.
It's this part.
So it's going to return the function pointer which is going to.
Well actually no it doesn't return it.
It calls it.
And this is a call so it calls the function thereby generating the appropriate result and we can experiment
with this approach and see how it all goes so armed.
Well actually no that's we don't have it yet.
Asteroid planet.
And then we can collide things collide planets with it.
Well we also want spaceship space
so we can collide our planet with space ship we can collide planet with asteroid that can collide spaceship
with asteroid.
Very sad situation.
And we can collide planet with itself.
We should do nothing.
OK so let's take a look at what this gives us.
OK so once again after you fix us behind the scenes we now have our set up so spaceship lands on our
planet asteroids burns up in the atmosphere.
Asteroid hits and destroy a spaceship and finally objects pass each of their harmlessly.
So this is a fairly reasonable kind of set up.
And this is the set up which effectively allows you to have multimethods.
Now once again you can see this monstrosity of overengineering because essentially having these Type-I
these and whatever is probably not what you want to have.
But what I'm trying to show here is that if you do want a situation of having a function which depends
at runtime on two arguments and tries to figure out there are types you can have your cake and eat it
too.
In this particular case.
So what you do in this case is as follows.
Let's just recap what's going on here.
First of all we implement CRT RTP So essentially we pass information about each of the types into the
base class and what this base class does is it implements a function which returns the type ID of whatever
type argument is passed then.
Now what we're doing with these Type-I these is we're making a map of them so that for specific pairs
of Type-I these we know what the outcome is we know which function to call and when.
When it comes to actually implementing this in a generic fashion all we're doing is we're doing a lookup
in the map in both directions.
And if we find something then we simply invoke on the function pointer and we get to call the function
that we actually need to call and if we cannot then we do something else some kind of deal.
So once again this is yet another fight against the limitation of in this case multiple dispatch.
But it is possible to overcome the expense of this extra engineering effort here.