0

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 Instructions 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?

Flutterish
  • 554
  • 1
  • 3
  • 18
  • Another option could be to use virtual methods (like I did in my [Tiny Calculator Project](https://stackoverflow.com/a/46965151/7478597)). – Scheff's Cat Apr 14 '20 at 05:26
  • And how does that avoid typecasting? – Flutterish Apr 14 '20 at 11:21
  • With virtual methods, you are able to deal with base class pointers without knowing the actual type of the AST node. This is how I called `solve()` for my AST in the above linked sample. – Scheff's Cat Apr 14 '20 at 11:24
  • But I don't have just one data type like double, i have a set of possible types it can return and/or accept. Also I already do have a virtual method, `A->GetType()->GetOperator( "+" )->Execute( { A, B } )` with signature virtual `Value* Execute( Value* values[] )` – Flutterish Apr 14 '20 at 11:25
  • Alternatively, you could return a `Data` type or an address to where the result is stored. – Scheff's Cat Apr 14 '20 at 11:28
  • I still need to typecast in that method to get the data i want to operate on – Flutterish Apr 14 '20 at 11:29
  • The question (to me) is whether you want to support static or dynamic typing. For static typing, data does not need to provide the type itself. Instead, the type results from the operation which returns the data. For dynamic typing, it's different. – Scheff's Cat Apr 14 '20 at 11:30
  • I guess that means I'll go with dynamic typing as that makes the most sense for what I'm making. I don't think any of the static solutions generalize well enough. – Flutterish Apr 14 '20 at 11:38
  • I would prefer static typing. The necessary implicit casts may be detected/inserted when building the AST. During execution, all the operators may "blindly" process their arguments without any type check. Of course, you need such operators for type casting and you have to detect (during setup of AST) where they are needed (and which one). – Scheff's Cat Apr 14 '20 at 11:42
  • Something like that?: [**Sample on coliru**](http://coliru.stacked-crooked.com/a/b6d56592bdee4e2c) – Scheff's Cat Apr 14 '20 at 12:08

0 Answers0