Quantity (template programming)
A. First Edition
This is all started by an email from Mr. Y who is such a guru in computer science and I really learned something from his
work. And it is also an assignment from Dr. Grogono who is such a programming language expert and I think one of the most
important course I took in Concordia is from his "principle of programming language". It is not the hardest one. Neither is
it a perfect one for me because when I took it I was completely unprepared for it. As I mentioned in my diary that I wish I
could be given another chance to face it when I was better prepared by exposing myself to more discipline of programming
language. Naturally this assignment is also a language-related problem which is so interesting that I even stop doing my
already-piled-up assignment.
My idea is a straight forward solution which is suggested by the hint in Mr. Y's email. I browsed his source and felt quite
impressed because he is using pure template to solve the "sub-class" problem while most people including professor would
take an inheritance approach. For example, for the Length family there are many non-standard units like foot, inch, mile
etc. How would you represent for them? Should we maintain the same "type-signature" by using "Quantity<1,0,0>" and let
those be derived classes with different class "name". (Here I create a concept of "type-signature" which means its type
represented by template parameter and class name. i.e. Quantity<1,0,0>)
However, I don't like this approach because it seems to me that even the non-standard units has different names and
properties, they don't introduce new methods at all! And what is the definition of class? A combination of data and its own
methods. If there is no new method to be created why should we introduce new classes? So, my solution is to regard those
non-standard units as instance of same class with different property of both name and "ratio" which is its ratio against
ISO standard units like "meter, kilogram, second" etc. For example, an entity of 3.5 feet would be declared as:
template <int L, int M, int T>
class Quantity
{
Quantity(string name, double ratio, double value);//constructor
};
typedef Quantity<1,0,0> Length;
...
Length q1("foot", 0.913, 3.5);
Another interesting thing to me is that I actually try to create dynamic type. You see, in C++, everything must be declared
before be used. Type is also the case. You cannot use a type without declare it first. But what do you think the following?
template<int L1,int M1, int T1, int L2, int M2, int T2>
Quantity<L1+L2, M1+M2, T1+T2> operator*(const Quantity<L1,M1,T1>& q1,const
Quantity<L2,M2,T2>& q2)
Do you think the new type "Quantity<L1+L2, M1+M2, T1+T2>" is declared before using? It seems to me yes at first glance.
Then just take a while for rendering and you may change your mind. To illustrate this more clearly, let's imagine that I
want to overload "operator *" as member method of class Quantity. Can you do that? Definitely not! Because your template
would be "template<int L, int M, int T>" instead of "template<int L1,int M1, int T1, int L2, int M2, int T2>".
The following is an excerpt from my email back to Mr. Y.
...
Sorry for late reply and actually I originally planned to reply when I did
finish more work. However, it seems my supervisor is assigning me new research
directions. (A more attractive one than previous pure theoretical pattern.) And
according to my experience that if I left a half-finished project for more than
two days, I will lose passion for it forever. So, better than nothing. I send my
solution which is inspired by yours and it is a more straight forward one.
1. My solution is nothing new but purely using (L,M,T) as a type indicator. And
for the derived quantity such as foot, seamile in Length, pound, ton in weight,
I use a property "ratio" to differenciate them. It is coming from a common
knowledge that all unit has an ISO standard unit and all other units has a
certain ratio to these standard unit. Since those units along with their
standard units all belong to same family, they should enjoy the same "type". So,
that's why all length units have a sole type of <1,0,0,> and each individual
"Length" sub unit has its own "ratio" property. This makes sense to common
programmer and there is no need for inheritance because these non standard units
don't introduce new method at all! The only thing they brings is their "name"
and "ratio" and even the "standard" unit like "meter, gram, second" also have
such properties. They are in effect same as other unit except their ratio is
"1".
2. I really hate writing long parameter when using template. For example, if we
declare a template like:
template <int L, int M, int T, int UL, int UM, int UT>
void somefun(){...}
When I use it, I have to write "somefun<l,m,t,l1,m1,t1>();". Even you can use
some "typedef" trick to hide some of them, still it is not a good idea. So, my
idea is to reduce the parameter as many as possible. So, this is another reason
I limited parameter of template to "<L,M,T>". However, there is one seemingly
impossible obstacle for "operator *" of two quantities. Originally I want to
overload operator * as "member method" of my class Quantity. Then it seems
impossible since the parameter needs introduce another group of L',M',T', but my
class is defined only by L,M,T:
template <int L, int M, int T>
class Quantity
{...};
So, I later found it out that I can only do it in a global function operator
overloading with six parameters!
template<int L1,int M1, int T1, int L2, int M2, int T2>
Quantity<L1+L2, M1+M2, T1+T2> operator*(const Quantity<L1,M1,T1>& q1,
const Quantity<L2,M2,T2>& q2)
{....}
However, programmer doesn't even know I introduce three extre parameter when he
uses this function like this:
Quantity<1,0,-1> q1;
Quantity<2,0,0> q2;
cout<<(q1+q2)<<endl;
My point is that I maintain my idea that "type" will only need three integer to
indicate.
3. Your way using index to indicate units is quite interesting and thoughtful.
However, I feel a bit difficulty in understanding it and I believe many people
feel the similar way. If a library is supposed to be used by common programmer,
it should follow the common sense of common people. And I feel people will find
easier to follow my idea. This is only my personal view and welcome your
comments.
4. I must admit I underestimated the trouble of "name" resolve. You can see I
spent most of my time to design a good way for a better name representation. And
eventually it is a half-finished work. My idea is to let "Quantity" class update
its name automatically when operator * or / is invoked between different type of
units. i.e. Speed.name="meter/second", Time.name="second". (Speed*Time).name="meter".
However, when units have name of square or volume, like "meter.meter/second",
"kilogram/meter.meter", the parsing becomes quite complicated. (At least I
haven't found a straight forward way.) I have listed all the subsets of three
unit in its first exponent. But I give it up.
...
And here is some of his comments:
And here it goes:
And here it comes:
There are some assumption in my program:
1. When two quantity is doing operation of + or -, the type is guaranteed by type checking at compile time.
2. When two different category quantity are doing operation * or /, there is no limit on type consistency which means
different type of quantity can multiply or be divided. i.e. weight/area, power*length. However, the final result type will
be decided by left-hand-side operand if two operands are different "sub-classes". For example,
inch* foot = inch.inch
And the value would converted appropriately according to its ratio.
3. The instance name should be input using delimiters of "." or "/".
i.e. Power q1("meter.kilogram/second", Ratio(1,1,1), 3500);
My program tries to maintain the name after operation "*" but it has big limit to first order of units. For example, units
like "pound/foot.foot" cannot be handled! I really feel hopeless and run out of time on this kind of coding.
E.Further improvement
It cannot be run on VC++6 and you need to run it on "visual2003".
F.File listing
1. quantity.h
2. quantity.cpp (main)
file name: quantity.h
#include <iostream> #include <string> #include <cmath> using namespace std; /* #define ADDITION 0 #define SUBTRACTION 1 #define MULTIPLY 2 #define DIVISION 3 */ template<int L1, int M1, int T1> struct QType { }; class Ratio { public: Ratio(double l, double m, double t):lRatio(l),mRatio(m),tRatio(t) { } double lRatio; double mRatio; double tRatio; }; //int operator()(int value) template<int L, int M, int T> class Quantity { friend ostream& operator<<(ostream& os, Quantity<L,M,T> & q) { os<<q._value<<"("<<q._name<<")"; return os; } protected: void retrieveNames(const string& _name, string names[3], int rank[3], int length)const { int iStart=0, iEnd=0; int current=0; //find first while (current<length-1) { iEnd=_name.find_first_of("./", iStart); names[rank[current]]=_name.substr(iStart, iEnd-iStart); current++; iStart=iEnd+1; } names[rank[current]]=_name.substr(iStart, string::npos); current++; while (current<3) { names[rank[current]]=""; current++; } } int sortOutOrder(int L, int M, int T, int rank[3])const; public: int _L, _M, _T; string _name; Ratio _ratio; double _value; Quantity(const string& name, const Ratio& ratio, double value) : _name(name), _ratio(ratio),_value(value) { _L=L; _M=M; _T=T; } //this is a fancy function which is not so critical but boring void resolveName(string names[3])const; string updateName(int L, int M, int T, string names[3])const; double convert() const { double result=_value; result*=pow(_ratio.lRatio, L); result*=pow(_ratio.mRatio, M); result*=pow(_ratio.tRatio, T); return result; } double unconvert(int L, int M, int T, double value) const { double result=value; result/=pow(_ratio.lRatio, L); result/=pow(_ratio.mRatio, M); result/=pow(_ratio.tRatio, T); return result; } }; typedef Quantity<1,0,0> Length; typedef Quantity<2,0,0> Area; //typedef Quantity<1,1,1> Energy; typedef Quantity<1,1,-1> Power; template<int L, int M, int T> int Quantity<L,M,T>::sortOutOrder(int L, int M, int T, int rank[3])const { int length; //I hate using a lot of "if then else" and I always prefer a linear order //which is safe and clear. //first case is three name in order of 0,1,2 //and we don't have the case of L<0&&M<0&&T<0 if (L>0&&M>0&&T>0 || L>0&&M>0&&T<0|| L>0&&M<0&&T<0) { rank[0]=0; rank[1]=1; rank[2]=2; length=3; } //three names and order is 0,2,1 if (L>0&&M<0&&T>0) { rank[0]=0; rank[1]=2; rank[2]=1; length=3; } //three name but order is 2,0,1 if (L<0&&M<0&&T>0) { rank[0]=2; rank[1]=0; rank[2]=1; length=3; } //three names and order is 1,0,2 if (L<0&&M>0&&T<0) { rank[0]=1; rank[1]=0; rank[2]=2; length=3; } //three names and order is 1,2,0 if (L<0&&M>0&&T>0) { rank[0]=2; rank[1]=0; rank[2]=1; length=3; } //similarly we don't have the case of both negative ones, i.e. L<0&&M<0&&T==0 //here goes the two names and order is 0,1, if (L>0&&M>0&&T==0||L>0&&M<0&&T==0) { rank[0]=0; rank[1]=1; rank[2]=-1; length=2; } //two names and order is 0,2 if (L>0&&M==0&&T>0||L>0&&M==0&&T<0) { rank[0]=0; rank[1]=2; rank[2]=1;//still I need this info length=2; } //two names and order is 1,2, if (L==0&&M>0&&T>0||L==0&&M>0&&T<0) { rank[0]=1; rank[1]=2; rank[2]=0; length=2; } //two names and order is 1,0 if (L<0&&M>0&&T==0) { rank[0]=1; rank[1]=0; rank[2]=2; length=2; } //two names and order is 2,0 if (L<0&&M==0&&T>0) { rank[0]=2; rank[1]=0; rank[2]=1; length=2; } //two names and order is 2,1 if (L==0&&M<0&&T>0) { rank[0]=2; rank[1]=1; rank[2]=0; length=2; } //one name only if (L>0&&M==0&&T==0) { rank[0]=0; rank[1]=1; rank[2]=2; length=1; } if (L==0&&M>0&&T==0) { rank[0]=1; //the rest order is meaningless rank[1]=0; rank[2]=2; length=1; } if (L==0&&M==0&&T>0) { rank[0]=2; //you should ignore these orders rank[1]=0; rank[2]=1; length=1; } return length; } template<int L, int M, int T> string Quantity<L,M,T>::updateName(int L, int M, int T, string names[3])const { string result=""; int rank[3]; int length; int temp; length=sortOutOrder(L, M, T, rank); result=names[rank[0]]; for (int i=1; i<length; i++) { switch (rank[i]) { case 0: temp=L; break; case 1: temp=M; break; case 2: temp=T; break; } if (temp<0) { result.append("/"); } else { result.append("."); } result.append(names[rank[i]]); } return result; } template<int L, int M, int T> void Quantity<L,M,T>::resolveName(string names[3])const { int rank[3]; int length; //I hate using a lot of "if then else" and I always prefer a linear order //which is safe and clear. //first case is three name in order of 0,1,2 //and we don't have the case of L<0&&M<0&&T<0 length=sortOutOrder(L, M, T, rank); retrieveNames(_name, names, rank, length); } template<int L, int M, int T> Quantity<L,M,T> operator+(const Quantity<L,M,T>& q1, const Quantity<L,M,T>& q2) { string resultName=q1._name; Ratio resultRatio=q1._ratio; double resultValue=q1._value+q2.convert(); Quantity<L,M,T> result(resultName, resultRatio, resultValue); return result; } template<int L1,int M1, int T1, int L2, int M2, int T2> Quantity<L1+L2, M1+M2, T1+T2> operator*(const Quantity<L1,M1,T1>& q1,const Quantity<L2,M2,T2>& q2) { string resultName; string names1[3], names2[3]; Ratio resultRatio=q1._ratio; double resultValue=q1.convert(), otherValue=q2.convert(); q1.resolveName(names1); resultName=q1.updateName(L1+L2, M1+M2, T1+T2, names1); resultValue*=otherValue; resultValue=q1.unconvert(L1+L2, M1+M2, T1+T2, resultValue); Quantity<L1+L2, M1+M2, T1+T2> result(resultName, resultRatio, resultValue); return result; } template<int L1,int M1, int T1, int L2, int M2, int T2> Quantity<L1-L2, M1-M2, T1-T2> operator/(const Quantity<L1,M1,T1>& q1,const Quantity<L2,M2,T2>& q2) { string resultName; string names1[3], names2[3]; Ratio resultRatio=q1._ratio; double resultValue=q1.convert(), otherValue=q2.convert(); q1.resolveName(names1); resultName=q1.updateName(L1-L2, M1-M2, T1-T2, names1); resultValue/=otherValue; resultValue=q1.unconvert(L1-L2, M1-M2, T1-T2, resultValue); Quantity<L1-L2, M1-M2, T1-T2> result(resultName, resultRatio, resultValue); return result; }
file name: quantity.cpp
#include <iostream> #include "quantity.h" using namespace std; int main() { string names[3]; string powerName="meter.kilo/second"; string footName="foot"; Power q1(powerName, Ratio(1,1000,1), 1.0), q2((string)"foot", Ratio(0.91,0,0),10.0); Length q3(footName, Ratio(0.91,0,0), 1.0); //cout<<q1<<" + "<<q2<<" = "<<(q1+q2)<<"\n"; q1.resolveName(names); cout<<q1.convert()<<endl; cout<<(q1*q3)<<endl; cout<<(q1/q3)<<endl; return 0; }
The result is like following:
How to compile?
1. first compile "idl" file by "idlj -fall Bank.idl".
2. Second compile server and client by placing server and client file into folders generated by "idlj".
javac ./BankServer/BankServer.java
javac ./BankClient/BankClient.java
How to run?
How to run orb:
start orbd -ORBInitialPort 2345
(And I presume this name server is running on a host name "argentina.encs.concordia.ca", see below explanation in server.)
How to run server?
Originally I plan to let all server to register its host name in "name server". However, the naming context is very complicated in CORBA that there is difference between "transient and persistence". I use the later one and all name context cannot be properly unbund. so, I give it up after a couple of days trial by hardcoding all host name in "BankServer". See below in "BankServer".
public static String branchHostNames[]={"chile", "uruguay", "argentina"};
So, the host name of server "Branch100", "Branch101", "Branch102" must have these three name as host name.
to run server "branch100" you have to run this server at a host name of "chile.encs.concordia.ca":
java BankServer.BankServer -ORBInitialHost argentina.encs.concordia.ca -ORBInitialPort 2345 100
to run server "branch101" you have to run this server at a host name of "uruguay.encs.concordia.ca":
java BankServer.BankServer -ORBInitialHost argentina.encs.concordia.ca -ORBInitialPort 2345 101
to run server "branch102" you have to run this server at a host name of "argentina.encs.concordia.ca"
java BankServer.BankServer -ORBInitialHost
argentina.encs.concordia.ca -ORBInitialPort 2345 102
How to run client?
java BankClient.BankClient -ORBInitialHost argentina.encs.concordia.ca -ORBInitialPort
2345