Implement dynamically typed data

Hi all,

I want to implement a dynamically typed data object in my project. The project implements a mathematical calculator in which different data types can be manipulated together after implicit conversions. The implicit conversions are supposed to be carried out in a bullet-proof manner. If a conversion is impossible, the computation is regularly aborted and the user informed.

The way I am thinking to implement this is with the use of static_cast and void pointers. I have a class DynamicallyTyped which returns upon request a pointer to the desired data type. This is like implementing "virtual void*" but for data types, rather than for function pointers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iostream>
#include<assert.h>

class DynamicallyTyped
{
private:
  void * theData;
public:
  enum{ typeUndefined, typeInt, typeDouble};
  int expressionType;
  DynamicallyTyped()
  { this->theData=0;
    this->expressionType=this->typeUndefined;
  }
  ~DynamicallyTyped()
  { this->FreeMe();
  }
  void AllocateType(int theType)
  { this->FreeMe();
    switch(theType)
    { case DynamicallyTyped::typeInt:
        this->theData= new int;
        break;
      case DynamicallyTyped::typeDouble:
        this->theData= new double;
        break;
      case DynamicallyTyped::typeUndefined:
        break;
      default:
        assert(false);
        return;
    }
    this->expressionType=theType;
  }
  int& GetInt()
  { assert(this->expressionType==this->typeInt);
    return * (static_cast<int*>(this->theData) );
  }
  double& GetDouble()
  { assert(this->expressionType==this->typeDouble);
    return * (static_cast<double*>(this->theData) );
  }
  void FreeMe()
  { int* intPointer; double* doublePointer;
    switch(expressionType)
    { case DynamicallyTyped::typeInt:
        intPointer=static_cast<int*> (theData);
        delete intPointer;
        break;
      case DynamicallyTyped::typeDouble:
        doublePointer=static_cast<double*> (theData);
        delete doublePointer;
        break;
      case DynamicallyTyped::typeUndefined:
        assert(this->theData==0);
        break;
      default:
        assert(false);
    }
    this->theData=0;
    this->expressionType=this->typeUndefined;
  }
};

int main()
{ DynamicallyTyped X;
  X.AllocateType(X.typeDouble);
  std::cout << "\nEnter a double value: ";
  std::cin >> X.GetDouble();
  std::cout << "\nX.expressionType: " << X.expressionType << "; double value: " << X.GetDouble();
  X.AllocateType(X.typeInt);
  std::cout << "\nEnter an int value: ";
  std::cin >> X.GetInt();
  std::cout << "\nX.expressionType: " << X.expressionType << "; double value: " << X.GetInt();
  // this should pop the assert:
  //std::cout << X.GetDouble();
  std::cin >> X.GetInt();
  return 0;
}


Any comments/suggestions? Can this be done better? Is there a way to do the same functionality as above with using the keyword virtual (or some other technique)?

Thanks!
Last edited on
In the Windows world, the same aim is achieved using a union. This means that you don't have to worry about memory allocation for the regular types (int, double, ...), but does make the size of an instance that bit larger.

The VARIANT struct is part of Windows Automation, and is a C struct. When working with variants in C++ it's more usual to use a wrapper class. Microsoft provides 3 itself: COleVariant (in MFC), CComVariant (in ATL), and _variant_t (in Windows SDK - this is actually provided by Visual C++, as part if its COM support).

Boost provides a template based solution (Boost.Variant)

Last edited on
If you don't need a given variable to change it's type once created, you could use a virtual function based approach for the caculation.

I think it would make the calculation logic clearer. But if the user wanted to change a (e.g.) double value to an integer one, you have to replace the existing object, rather than just update it.

Andy

P.S. Have you come across factories?
Last edited on
No, what are factories?

Internally, I make heavy use of built-in static type checking. The data container is not supposed to be used for computation, i.e. I will not be adding two dynamic data types. I plan to use it like this:

Z.GetPolynomial()=X.GetPolynomial()+Y.GetPolynomial();


The double/int example was just to simplify concept. In reality the calculator has 20 different types (it actually does not have typeDouble - I am an algebraist, hehe), some of which can grow to many MB's of size.

1
2
3
4
5
6
7
8
enum{typeUndefined=0, typeIntegerOrIndex, typeRational, typeLieAlgebraElement, 
  typePoly, typeRationalFunction, typeUEElementOrdered, //=6
  typeUEelement, typeWeylAlgebraElement, typeMapPolY, typeMapWeylAlgebra, typeString,
  typePDF, typeLattice, typeCone, //=14
  typeArray, typeQuasiPolynomial, typePartialFractions, //=17
  typeFile, typeDots,
  typeError //typeError must ALWAYS have the highest numerical value!!!!!
  };


Last edited on
A factory is a class (or function) that knows how to create one or more classes.

If you go the virtual route, it would a way of get the right type, along the lines of:

class ValueFactory
{
public:
static Value* createValue(int value);
static Value* createValue(unsigned int value);
static Value* createValue(double value);
...
};

where Value is the base class of the specialized value type classes.

But given you're latest post, this is heading in the wrong direction.

Andy

P.S. to avoid having to warn people that "typeError must ALWAYS have the highest numerical value!!!!!" you could define it as follows:

enum{typeError=-1, typeUndefined=0, typeIntegerOrIndex, typeRational, typeLieAlgebraElement, typePoly, ...

Unless -1 would confuse code elsewhere?

Can you given a simple polynomial based example?
Thanks a lot for the discussion... I don't get your last question?

On the topic of type conversion: my implementation is very messy (for example, typeRationalFunction may or may not be convertible to typePoly: you have to run a heavy-duty function and based on its output either succeed or fail).

I do not maintain a complete conversion table. Instead, I maintain a directed graph, in which each type remembers the "next strongest" types. So, when I convert typeIntegerOrIndex to typeRationalFunction, I actually call the conversion routine 3 times:

typeIntegerOrIndex->typeRational->typePoly->typeRationalFunction
Last edited on
I was looking for a simple example of what

Z.GetPolynomial()=X.GetPolynomial()+Y.GetPolynomial();

evaluates to.

So...

If X = typePoly : "x2 + 2x" and Y = typePoly : "-3x + 1"

Does Z ends up as typePoly : "x2 - x + 1"

Andy
Yes but that is way too simple. The polynomials are multivariate.

X=x_1x_2+2x_1+1
Y=x_1+x_2^2

X+Y=x_1x_2+x_2^2+3x_1+1

Btw. an experimental prototype of the calculator is already up and running (online via CGI interface+apache server):

http://vector-partition.jacobs-university.de/cgi-bin/calculator

Just that the data inside each "dynamic data" type is stored as a separate member of the class "union method" that you described. This is a problem because it is way too big and creates trouble on large expressions. (20 types times 4 bytes for each pointer is 80 bytes, which means 1 000 000 nodes are >8MB. This is actually a very non-trivial issue. In addition, I plan to expand the number of types by quite a bit).
Last edited on
Why 20 times 4 bytes?

If you have a union of assorted types, the size of the union is that of the largest type!
Oh, I have misunderstood what assorted type is then - and I clearly don't know what union is! Well, is it a windows-only feature (I run Ubuntu on my comp and the server runs SUSE)? Can you give a very short example?
Oops:
http://www.cplusplus.com/doc/tutorial/other_data_types/
- reading it ATM

[Edit]: Got it! So, I can replace the void* in the original suggestion with a union, and avoid the static_cast's altogether; this sounds quite more elegant.
Last edited on
Topic archived. No new replies allowed.