I've heard, and seen why typecasting is usually a sign of bad class hierarchy / design, unless it's from an outside library that you don't have control over. However in this scenario I'm not sure how to avoid it, at all.
I'm designing a little program that executes code based on an abstract tree. All values are derived from a single base class Value
. Now I want to add them, so first of all, typecheck if that's allowed - A->GetType()->HasOperator( "+", { A->GetType(), B->GetType() } )
. Probably unnecessary if I statically analise the tree. But it's actually the next step that's the problem. Say, A->GetType()->GetOperator( "+" )->Execute( { A, B } )
with signature virtual Value* Execute( Value* values[] )
. What now?
The Value
type itself can't really hold any value, because it's the derived classes like Number
that know how and what type of information they have ( of course this only applies to primitive types, not user defined ones, these are easier to deal with after you know how to deal with primitives ). I can't really define the +
operator for 2 number types because that still leaves me with 2 Value*
s that I have to pass to that method and then I still have to convert them to Number
in that method.
One solution that popped into my head is - just make the Value
have binary information, deal with primitives in binary and... First of all, that's just typecasting with extra steps, on a smaller scale. Second, that's a lot of work. Third, might as well build a binary machine.
Second solution is to explicitly define all primitive operations. So, Number* AddNumberToNumber ( Number* A, Number* B )
, Number* GetNumberField ( Object object, string name )
etc. for all primitives. Seems tedious and I'm not sure if that's even possible to store in a reasonable way. Right now the code execution works like a stack - each Instruction
after executing leaves more Instruction
s on the code stack. So, if I were to do this, I'd have to define a whole derived class for each operator instead of a single delegate each. ( I think this works, but it doesn't generalize well and if I were to parse text to code, I would need to statically analise it and have an if..else clutter for each operation instead of, say a LUT Operator -> [ List of accepted types, Delegate ]
. In other words, I think this solution would be a mess ). I'd rather have this or something similar:
standardOperators = new map< ... > {
{ "+",
{ { types::number, types::number }, [ some delegate ] },
{ ... },
...
},
{ "-",
...
},
...
};
So, is there a better solution? Or is typecasting just the better option at this level of tinkering?