So far all of the approaches that we've taken to actually providing printing functionality for our very
simple hierarchy of expressions have been sort of half measures what we really want is we really want
a mechanism which will allow us to extend the entire hierarchies functionality in various different
ways without being intrusive and certainly without having massive IF statements full of dynamic casts
in them.
So how can we have our cake and eat it.
Well unfortunately there is no clean way of getting it but we are going to get it using something called
double dispatch.
Now the reason why the double dispatch approach exists is because it is impossible to perform a dispatch
at runtime at least in C++ own C++.
If you have a dash great to then left.
This is an expression pointer and that runtime nobody's going to try and automatically figure out if
it's an additional expression or a double expression pointer.
Nobody is going to do those checks for you.
So the only way to actually perform those checks at compile time is to trick the system into doing so
and this is precisely what the double dispatch approach does.
So it's not the nicest thing in the world but it's reasonable shall we say.
So what I'm going to do is I'm going to have I'm actually going to implement two different concerns
so one is printing that's something we've done already and we're going to redo it using double dispatch.
Another is evaluating that expression and calculating the final value.
This will hopefully illustrate better how the whole thing can be instrumented in terms of the double
dispatch approach.
So what are we going to do and how are we going to do it.
Well first of all I'll go into expression and I'll add something different.
So forget about the virtue of the structure.
I mean you can keep it there but we're going to have something specific to this double dispatch approach.
Now that specific thing is a virtual void function called Except now it's typically called Except almost
by convention.
So most people would call it except.
And what this function would take is a pointer to an expression visitor visitor.
So what does an expression visitor want an expression visitor is.
A component which is able to visit every element of the expression Harkey and what this means in practice
is that you have an interface so expression visitor in an interface similar to the following.
So you'll have an expression visitor and you'll have a virtual void function called visit for every
single element in the Harkee So we have two different elements.
We have a double expression.
And in addition expression.
So you'll have a double expression here.
And there's going to be pure virtue.
And similarly you'll have an addition expression
like so.
So this is the interface.
And whenever you want printing or evaluation or something else you would have to implement this interface.
So for example suppose I want an expression printer what I would do is I would make an expression printer
like so which would be an expression visitor and then I would have to implement this interface.
And by the way these things double expression is an expression I suppose I have to forward declare them
because otherwise it's not going to compile so I'll have double expression.
And let's have similarly an expression here.
OK so our expression printer is going to implement this interface.
And what we're going to do here is we are going to.
Well actually let's not jump the gun let's not jump the gun and furthermore I'm actually going to implement
them outside the class.
Let's just put semi-colons in here for now so I do want them but I want them to appear elsewhere.
Now the idea here is as follows.
If we do get an expression it's a once again we're going to have an output string stream
and we're also going to have a function called S-Corp which returns a string for journo's as to test
the air.
OK so now about these visit methods of ours what do we want to do.
Well essentially what's happening here is a double hop.
So essentially when you have these visit methods what happens inside them is the actual thing that you
want to do.
But the interesting thing is how they are invoked.
So if we go into expression and if we actually go implement except because we have to implement except
it's a pure virtual function so we have to implement it here and we have to obviously implement that
here as well.
Otherwise it's just not going to compile.
I'm afraid the idea is that inside this except the same thing happens the same thing happens in every
single one we take the visitor and we invoke visit on it with the pointer to the object that we're currently
in.
Now I'm going to copy it and also paste it here.
So you might be thinking Well hold on Demitri Why are we replicating the same code in every single inheritor
of expression if we can just stick it into expression.
Well unfortunately we cannot because we need the type of the this pointer we need the type of this pointer
and the type is different here and here and depending on the type of this pointer we're going to be
invoking either this visit or this visit.
And that's the double dispatch approach.
That's the double hop.
Essentially we are kind of treating the system to pick which one of the overloads we're actually calling
by passing this pointer from inheritance.
And not just defining it as part of the overall expression.
So this is how the game goes you could trick the game you could have one implementation of except inside
the expression but then you would have to pass in your type via the curiously recurring temporal pattern
which is a bit more complicated because you would be writing expression of double expression an expression
of an expression so it is possible is just even uglier than what we are doing.
And I have to admit that what we're doing is pretty ugly.
Unfortunately we were kind of Sercombe navigating some of the limitations of static object oriented
languages here.
OK so we have these calls to visit and visit is precisely where the actual implementation happens so
I'm going to get rid of this expression printer that's the old stuff that we had.
And we're going to basically redefine everything.
We're going to redefine the printing once again and I hate to repeat myself over and over.
But essentially we're changing the approaches on each turn.
So now what we can do is we can actually go ahead and implement the methods of the expression printer.
So let me see if I can generate the definitions for those to generate the definition for this one.
And then I will generate the definition for this one now I think they need to go after the objects of
the printing because we're going to need their insides.
So I'm going to cut them and I'm going to stick them towards the very end here.
Let's put them in here.
OK so the actual printer.
What it does for the double expression and the expression is pretty much the same as what we had previously.
So for example for the double expression you would just say oh assess the expression value and for the
expression printer Well we can certainly print the.
We can certainly print the opening brackets.
Then perform the.
Actually let me show you before we go anywhere let me actually show you how we would go about doing
this.
So essentially we would print.
So I would take o recess and I would print the opening round bracket for example and then when I want
to print the left side for example below do is I would take a IID that way to then I would take the
left side and then I would call except on it and I would call an except with guess what I would call
them except with myself.
So this is how you would actually go about printing the left side of your current expression then you
would do the same for the right side of the expression as well.
Well let's actually make this more complicated.
I want to show you some of the practical concerns that you have when printing expression.
So let me add another element to the Harkey.
I know I'm overcomplicating things but it's going to be it's going to be a lot of fun.
So in addition to that addition expression pardon the repetition here.
We're also going to have a subtraction expression which is going to be almost identical so we actually
copy this.
So I'm going to have a copy of this whole thing and it's going to be called subtraction expression.
You actually just do like this subtraction expression.
Copy it here.
Copy here.
Obviously the destructor as well.
Tida And of course you can see the red underlining here that we don't have a matching function.
So now we have to go in here and also replicated replicated here as subtraction and expression.
And then of course in every inheritor is also there is no avoiding it.
So we get to implement a subtraction expression and this time round it's put it towards the end which
is very nice I think.
So the line is slowly figuring out how I want to do things.
OK so why did I add subtracting expression.
Well what I wanted to show you is being contextual in terms of putting the round brackets not just putting
them everywhere but putting them only when it's relevant So let's suppose we have the following expression.
We have an expression which is an additional expression composed of 1.
So a double expression containing 1 and a new subtraction expression which contains two double expressions
of expression containing two and a double expression containing three for example.
So obviously if you have one plus two plus three you don't need brackets anywhere because it's all associated
Right.
But if you have one plus the result of two minus three you won the brackets around two minus three so
Center you want something like 1 plus and then brackets to minus three.
So the question is well how can you figure out where exactly you want the brackets and this is what
I'm trying to do kind of get at here because what I want to show is that even though we've now got this
very nice approach Well we can print most things in the sense that here for example I can do this as
opening round bracket and I can well I've already shown you how to actually print the whole thing.
What we need to do is we need to determine the kind of expression that this is and only add braces if
we if we actually need to.
And by that I mean we only have braces if the right hand side of whatever expression we're in at least
in the subtraction part if the right hand side is actually subtraction suppression.
So here you would say something like you need braces equals dynamic cast subtraction expression pointer
and you would take well in this case it should be s e not a.
Let's see if we can rename this using the refactoring as it is much better.
Ok so I would try to cast as E to A S E right to a subtraction expression.
And if I succeed well then I need braces so if I need braces sounds like I need to teach job or something
if I need braces then well we put in the opening parenthesis here and then we say se left.
Except this.
So this is the double dispatch barthe of the whole thing.
Then we add the minus.
Then once again we add the right part.
E right.
Except this.
And then finally if we need braces then we add the closing Brumback and so this is how you would do
subtraction for example.
And in a similar way you would do addition because there you would also have to check the right side.
I'm not going to bore you with actually implementing this I'm just going to paste this in here it's
a very similar kind of implementation anyway.
So the name seems to be e here instead of a.
Which isn't really that important.
So we've set up both of these and as a result what we can do is we can actually print this whole thing
and just check whether it actually works or not.
So here for example I can make an expression printer and then I can say printer don't visit so notice
we use visits instead of whatever it is that we're doing.
So here I can feed anything I can feed any of the pointers I can feed the pointer in here without caring
what it actually is because kind of cover every single case almost by definition and then I can see
out if you don't test your life.
All right let's see what we got here.
All right so as you can see I made one minus two minus three and hopefully I got the right thing.
Let me just verify that what we have is.
Well hold on.
They should be ed. expression and inside the ed. expression.
Yeah that should be a plus obviously but apart from that you know the bracket positioning is actually
correct so I'm going to just just wave away on this one because I want to show you something else.
So we figured out whether we need brackets or not.
I want to show you how you could evaluate this expression then this goes towards the extensibility and
the adhering to the open closed principle.
So once you've got the expression printed all you have to do is basically inherit that.
So instead of just jumping into it or something you inherited and you make your own evaluator.
So for example I can make an expression evaluator which is also an expression visiter And here what
I can do is I can store some result and I can evaluate the entire expression basically as soon as I
figure out how to type.
OK so here you have to implement the functions
and well I'm not going to do them in line by the way I am.
I still have to have them after the definitions of the different expressions because otherwise I'm going
to compile.
So let me just get rid of this briefly and I will generate them somewhere towards the end of the file
hopefully.
OK.
So let me see if I can do them in bulk.
I wonder.
No it doesn't look like I can.
OK.
Let's generate them one by one and then I'll cut them and stick them towards the end of file so change
this and generate this OK so I'm going to move them towards the end of the file and hopefully we get
our expressions to actually evaluate.
So stick them in here.
All right so how are we going to evaluate those expressions.
Well it's actually not that difficult to be honest I mean if you need to evaluate double value that's
just that value.
So a result is equal to de-value.
That's the easy part.
But for the addition and subtraction it's a bit more complicated.
So we certainly have to value with the left hand side.
So let's evaluate the left hand side by calling except with this pointer.
And then what we do is we save the results I say auto tempy equals result because remember we write
everything to this result variable.
Then we evaluate the right hand side
and then we add two of them together.
Or in other words I take the result and I add the temporary to it because we've just written the result
with the right hand side and we on the left hand side to it and that's our thing.
And similarly I'm just going to copy this and paste it in here.
The only difference is that you have to have a minus in here.
All right so let's see if this expression evaluator actually works and the way we're going to do this
is I'll make an expression evaluator let's call it evaluate and then I will evaluated Daut visit with
once again passing a pointer E and then I can see out that it's actually we use this string.
So I can see out that he P-Dog SDR is in fact equal to.
And then evaluate her results and.
OK let's see if we can do this now.
All right so as you can see off-Terra tiny fix that I did here he didn't implement this correctly but
after the tiny fix we get the right results one plus two minus three is in fact equal to zero.
So this is how you can use this double dispatch approach to basically implement whatever visiter you
want.
So you have your baseline kind of interface here the expression visiter and then you simply inherit
from it and provide additional implementations.
So this is admittedly a clumsy approach but it's the only approach that we have to actually having our
cake and eating it in terms of being able to extend the functionality of entire Sharky's of objects.