C++ Potential

Pages: 12
@helios & Xander
Yes the group of heterogenous objects (buttons, textboxes, images) that share seemingly similar functionality (redrawing) is when the virtual keyword makes sense.

However, I have never found a use for this programming trick - probably because it is almost never needed for math applications (can you give a math related example of the virtual keyword?) - and for GUI I use ready-made solutions (the best of which, of course, is outputting html strings with javascript in them).

@helios:
Indeed, that seems to be a good idea. For evaluating my parser I use the following pattern:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Node
{ 
std::vector<Node*> children;
int OperationType;
int ExpressionType;
void Evaluate()
{ for(int i=0; i<this->children.size(); i++)
    this->children[i]->Evaluate(); 
  switch(this->OperationType)
  { case Node::typePlus: /*...*/
    break;
    case Node::typeMinus: /*...*/
    break;
    case Node::typeMinusUnary: /*...*/
    break;
/*...*/
    default: assert(false);
    break;
  }
}
enum OperationTypes{ typePlus, typeMinus, typeMinusUnary, //...
};
//...
};


I will think it over whether your scheme is better/easier to use...

... well at first glance the two schemes kinda look equivalent... (for example, my default: assert(false); corresponds to your initialization virtual int evaluate()=0;).

Another note: I am thinking to redo my Node class to actually contain its children:
1
2
3
4
5
class Node
{
public:
std::vector<Node> children;//rather than std::vector<Node*> children;
};

This is because I have some functions when I want my Node to create/destroy its children at will. So if each Node is owner of its children, I don't have to worry how to manage the memory.
Last edited on
Well, the point of polymorphism is precisely to avoid type-based branching and union-like structures. Or, at least, to let the compiler generate that code for you.
I was thinking, in which of the versions you write less code?

I am not 100% sure the I am right now trying to figure whether the virtual solution is less coding...

[Edit:] As for union-like structures: well, I have implicit type conversion system. How would you do implicit type conversion? My solution ain't very elegant, but I don't see how it would work easier with polymorphism (in fact, wouldn't it be more difficult?).
Last edited on
It's not just to code less. For example, the branching has to be repeated in every function that uses the type polymorphically. There's no room for error if you use linguistic polymorphism.
I didn't get this last statement - can you explain?
Last edited on
[Edit:] As for union-like structures: well, I have implicit type conversion system.
What do you mean?

Following your example, if you have more than one function like Evaluate() that uses the OperationType member, each of those functions will need to repeat the switch statement. It's not hard to see how this leads to a lot of repetitive and error-prone code.
It's not hard to see how this leads to a lot of repetitive and error-prone code.

The default: assert(false); so far managed to catch all such mistakes (at least I hope so)- in my experience I didn't lose much time with this switch-design. That doesn't mean yours isn't better of course.

The real mess comes with implementing arbitrary functions. I don't believe it is possible however to get any clean solution there (actually, this problem is hard for humans too, not only for computers).

Imagine you have a function
gaussianElimination( (Rational,...),...)
that takes a matrix of rationals as input.

Now
 
gaussianElimination(  (1,1), (1, 2)   )

(2 by 2 matrix)
should work equally well as
 
gaussianElimination(  (1, 2)   )

(1 by 2 matrix)
should work equally well as
 
gaussianElimination(  1, 1   )

(2 by 1 matrix)

However, this should cause an error:
 
gaussianElimination(  1, (1,2)   )

I don't believe there is a sane way to catch such an error at parsing time. This must be an evaluation-time-error.


[Edit:] As for union-like structures: well, I have implicit type conversion system.
What do you mean?


Let me illustrate by example:

Imagine I need to parse & evaluate

2 + x_1

After parsing, my expression tree looks like:

1
2
3
4
5
Y0 = Y1 + Y2     Expression type: (?) we dont know yet, at the end will be Polynomial
Y1 = 2           Expression type: Integer (must be converted at the end to Polynomial)
Y2= Y3 _ Y4      Expression type: (?) we dont know yet, at the end will be Polynomial
Y3= the letter x Expression type: Letter
Y4= 1            Expression type: Integer


Now when I carry out Y0=Y1+ Y2, I need to carry out an implicit conversion of Y1 from Integer to type Polynomial (my default polynomials are with rational coefficients). So, besides the

"Y1.Evaluate()"

I also call

"Y1.ConvertToType(typePolynomial, impliedNumberOfVariables)"

The newly produced value of Y1 is the constant polynomial 2. I store the newly produced value in the very same node Y1, but change the expressionType.

This is why I think it is simple to have the all-in-one solution (say, by using unions; I use another uglier solution at the moment).
Last edited on
default will only catch omissions, not duplications. And it's still a lot to maintain if something ever changes.

If I understand what you're doing right, the solution in the interpreter pattern is to simply make Integer a derived type of Polynomial. That would correspond to
polynomial: integer;
Unless there's some specific reason why you need to do these types of conversions.
Conversion is needed x_1+x_2+x_3+x_100 is a 100 variable polynomial. There is no way to know the number of variables at compile time - this is user input.

I tried originally the approach where I count the implied number of variables for the whole expression. Then I needed to implement functions that actually make the implied number of variables *decrease* (for example, polynomial substitutions). The only way to know exactly how many implied variables in the expression there are is well ... to evaluate the expression.

So yes, I always need to convert.

P.S. this thread is officially hijacked >:)

P.S. 2) You are right, it is a mess to maintain, so yes, I will give your advice some serious consideration and maybe even do it (gradually of course)
Last edited on
Your reply seems somewhat impertinent relative to my question, but anyway, run time-defined types is the whole point of polymorphism. I think you should definitely see an overall improvement by switching to an OO design.
Well, sorry for sounding impertinent (<-had to look it up in the dictionary): I got your point - however such a large code rewrite can't happen overnight (and definitely not tonight).

At any rate, if you feel like shooting the breeze on the topic more, how would go about solving implicit type conversion (as explained by the example I gave, allowing for polynomials with arbitrary number of variables) using polymorphism tricks?
Last edited on
"impertinent" as in not obviously relevant, not as in rude.

Unless I'm missing something, a class structure like
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Polynomial{
    std::vector<Term *> terms;
};

class Term{
    Real coefficient;
    Polynomial *variable;
};

class Variable:public Polynomial{
    unsigned variable_index;
};

class Constant:public Polynomial{
    Real value;
};
should do the trick.

Following your example, if you have more than one function like Evaluate() that uses the OperationType member, each of those functions will need to repeat the switch statement. It's not hard to see how this leads to a lot of repetitive and error-prone code.


Consequently, if you use virtual dispatch and you have more than one subclass of the OperationType class, each of these subclasses need to supply implementations of all the same base virtual functions. It's not hard to see how this leads to a lot of repetitive and error-prone code.

Actually both switch and virtual are dual aspects of polymorphism. Switch scales well with the number of operations. Virtual methods scale well with the number of types. None of them scales well with both. You have to chose wisely instead of believing the OOP propaganda that switch sucks.
Last edited on
You're contradicting yourself.
[if] you have more than one subclass of the OperationType class, [something negative happens]
Virtual methods scale well with the number of types.


Can you give an example where, according to you, switch would work better than virtual dispatch?
In every case when you have a long list of operations that you constantly extend, or you require to dispatch on more than one type at once.

A concrete example:
In our simulation software the circuit is split into two parts: digital and analogue. They share lot in common, e.g. their interface is very similar - some operations are just the same, and some are not. However, because the interface is often extended by new operations, and there will be no third subclass, the switch approach is much more flexible.

Another example: expression simplification engine. The approach with a virtual base "simplify" method is very naive and limited. The only problem with switch in C++ is that switch in C++ (and Java) is itself very limited and has not a 0.01% of the power of proper pattern matching engine.


You're contradicting yourself.


I'm not. You haven't read carefully enough. Once again: Imagine a hierarchy with N subclasses with M virtual operations.

Add one operation to it. If you use virtual - you have to modify N pieces of code. If you use switch - you have to modify only one. Switch > virtual.

Now add a new class to it. If you use switch - you have to modify M pieces of code. If you use virtual - you have to modify only one.
Virtual > switch.

They are duals.
Last edited on
That's not an example. It's a description of a range of examples.
To rephrase my problem in rapidcoder's terminology:

my parser/evaluator has both lots of subclasses (types I call them - 21) and lots of operations - (23 are tokenized but only around 10 are actually used).

I plan on adding both new operations and new types.

What operations are allowed between which types is question of mathematics; sometimes a natural conversion exists but I simply haven't implemented it. Sometimes a conversion between types might exist/not exist based on the actual object data.

As a matter of minimal grammatical discipline, I [Edit:] *try to* allow operations only between objects of the same type. So, if the inputs of an operation are of different types, I try to make an implicit type conversion to one of the two types. Whenever I need to carry out functions with inputs of different types, I use a special operation (function) which has a separate run-time type checking. However, that is an entirely different, very messy, story (hinted in my long post above).
Last edited on
I'm not. You haven't read carefully enough. Once again: Imagine a hierarchy with N subclasses with M virtual operations.

Add one operation to it. If you use virtual - you have to modify N pieces of code. If you use switch - you have to modify only one. Switch > virtual.

Now add a new class to it. If you use switch - you have to modify M pieces of code. If you use virtual - you have to modify only one.
Virtual > switch.

They are duals.
Alright. This is much more descriptive than "scales well".
I would still argue that in the first case virtual dispatch isn't worse than switch. You're just spreading out the modifications over a larger area. Plus the compiler can make sure for you that you haven't forgotten to modify one of the N types. But I admit that for very few types (say, 5 or fewer) a switch is preferable.

my parser/evaluator has both lots of subclasses (types I call them - 21) and lots of operations - (23 are tokenized but only around 10 are actually used).
Are you sure you aren't confusing operations in the language (such as '+') with operations in the evaluator (such as evaluate() or ~Type())?
Let me count:

9 10 regular operations:

?-? Minus
-? Minus unary
?+? plus
?*? times
?/? divide
[?,?] Lie bracket
?_? underscore (used in combination with letters and planned as the c++ operator[])
?^? power
(?,?,...) array/list of variable size
?\mapsto ? (the LaTeX code for "->") - used to describe substitutions

+ 4 7 letter operations which take no arguments and might be merged into a single operation + evaluation

d letter for differential
x letter for variable
g,h,c,f letters for Lie algebra generators
i special token related to my personal experiments

+ 1 extra operation
function - arbitrary function with a name according to a look-up table and input-type specification, initialized by a string.


Are you sure you aren't confusing operations in the language (such as '+') with operations in the evaluator (such as evaluate() or ~Type())?


The two are quite blurred in my mind. I started with the firm intention of keeping the parsing phase and the evaluation phase completely separate.

In practice, it turned out that I have parts of evaluation in the parsing (i.e. parsing large constants), I have a pre-parsing phase (since the input comes as a CGI mess), I have a one-and-a-half stage evaluation - i.e. run-type checking (which would better fit into the parsing phase), and I have a pre-pre parsing stage evaluation stage in which I initialize the run type-checking by parsing (here the word "parsing" refers to a completely separate set of functions) strings that describe the allowed function arguments.
Last edited on
Topic archived. No new replies allowed.
Pages: 12