By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
440,393 Members | 1,405 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 440,393 IT Pros & Developers. It's quick & easy.

polymorphism and binary operations on objects

P: n/a

Cracow, 20.09.2004

Hello,

I need to implement a library containing a hierarchy of classes
together with some binary operations on objects. To fix attention,
let me assume that it is a hierarchy of algebraic matrices with the
addition operation. Thus, I want to have a virtual base class

class Matr;

and some derived classes:

class Matr1 : public Matr;
class Matr2 : public Matr;
class Matr3 : public Matr2;

etc.

I want to be able to write the code like the following one, which
intensively uses polymorphism:

Matr* A = new Matr1();
Matr* B = new Matr2();
Matr* C = new Matr3();
..
..
..
Add(A, B, C);

[or C->Add(A,B); ]

where the addition is supposed to be implemented either as a global function
that adds A to B and places the result into C, or as a virtual method
of a base class, which calculates the sum of A and B, and puts the result
into the object which calls the method.
In the first case the function(s) would have to be friends of the Matr1,
Matr2, etc. class(es). The same could be done using overloaded operators +,
but this is a secondary issue for my question, see below. I also abstract,
at the moment, from how exactly the binary operation is realised (for
example, one might use, or not use, expression templates).

The problem with the above is that each of the matrices
Matr1, Matr2, Matr3,... may require a different implementation
of the addition operation. For example, adding a diagonal matrix to a
dense matrix is possible, but is done differently from adding a dense
matrix to a dense matrix, or diagonal matrix to a diagonal matrix.
Thus, I may need to have a number of versions of Add():

Add(Matr1 *A, Matr1 *B, Matr1 *C);
Add(Matr2 *A, Matr2 *B, Matr2 *C);
Add(Matr3 *A, Matr3 *B, Matr3 *C);

but also

Add(Matr1 *A, Matr2 *B, Matr3 *C);
Add(Matr1 *A, Matr1 *B, Matr2 *C);

and a number of other combinations. This partially satisfies the design goal,
but one has to be very careful about casting the pointers prior to
calling Add(), in order to ensure that a suitable variant will be called.
Instead of writing

Add(A, B, C);

I would then have to write

Add( dynamic_cast<Matr1 *>(A),
dynamic_cast<Matr2 *>(B),
dynamic_cast<Matr3 *>(C) );

which is obviously inconvenient, and also invalidates the idea
of polymorphism, as the run type of the objects must be known
at compile time. In addition any extension of the library becomes
difficult, because if I define a new matrix type, together with
new Add() variants needed for it, the new Add() functions will
not be known as friends of the respective classes in the library.
Implementing Add() as a class method would also be difficult, because
there would have to be many overloaded variants in every class, the number
increasing with every new derived class.
A possible alternative might be to define one global function

void Add(Matr *A, Matr *B, Matr *C);

(or class method), and perform the run-time type checking within this
function. This would require having several if-else options within the
function. However this solution appears not very elegant, again somewhat
inconsistent with the idea of the polymorphism, and similarly hard to
extend on new matrix types (apart from being likely inefficient).
In view of the above, my question is:

What else can be done, using C++ capabilities, to allow me
just to write

Add(A, B, C);

and at the same time be able to extend the library
without having to make changes in the library?
Sincerely,

L.B.
*-------------------------------------------------------------------*
| Dr. Leslaw Bieniasz, |
| Institute of Physical Chemistry of the Polish Academy of Sciences,|
| Department of Electrochemical Oxidation of Gaseous Fuels, |
| ul. Zagrody 13, 30-318 Cracow, Poland. |
| tel./fax: +48 (12) 266-03-41 |
| E-mail: nb******@cyf-kr.edu.pl |
*-------------------------------------------------------------------*
| Interested in Computational Electrochemistry? |
| Visit my web site: http://www.cyf-kr.edu.pl/~nbbienia |
*-------------------------------------------------------------------*
Jul 22 '05 #1
Share this Question
Share on Google+
4 Replies


P: n/a
Leslaw Bieniasz <nb******@cyf-kr.edu.pl> wrote in message news:<Pi*******************************@kinga.cyf-kr.edu.pl>...
Cracow, 20.09.2004

Hello,

I need to implement a library containing a hierarchy of classes
together with some binary operations on objects. To fix attention,
let me assume that it is a hierarchy of algebraic matrices with the
addition operation. Thus, I want to have a virtual base class

class Matr;

and some derived classes:

class Matr1 : public Matr;
class Matr2 : public Matr;
class Matr3 : public Matr2;

etc.

I want to be able to write the code like the following one, which
intensively uses polymorphism:

Matr* A = new Matr1();
Matr* B = new Matr2();
Matr* C = new Matr3();
.
.
.
Add(A, B, C);

[or C->Add(A,B); ]

where the addition is supposed to be implemented either as a global function
that adds A to B and places the result into C, or as a virtual method
of a base class, which calculates the sum of A and B, and puts the result
into the object which calls the method.
In the first case the function(s) would have to be friends of the Matr1,
Matr2, etc. class(es). The same could be done using overloaded operators +,
but this is a secondary issue for my question, see below. I also abstract,
at the moment, from how exactly the binary operation is realised (for
example, one might use, or not use, expression templates).
You should consider a static method as an alternative to the global
method:
Matr::Add(A,B,C);
I prefer this as it makes things clearer.

Then you could experiment with different implementations of a static
method in different derived classes:
Matr1::Add(...);
Matr2::Add(...);


The problem with the above is that each of the matrices
Matr1, Matr2, Matr3,... may require a different implementation
of the addition operation. For example, adding a diagonal matrix to a
dense matrix is possible, but is done differently from adding a dense
matrix to a dense matrix, or diagonal matrix to a diagonal matrix.
Thus, I may need to have a number of versions of Add():

Add(Matr1 *A, Matr1 *B, Matr1 *C);
Add(Matr2 *A, Matr2 *B, Matr2 *C);
Add(Matr3 *A, Matr3 *B, Matr3 *C);

but also

Add(Matr1 *A, Matr2 *B, Matr3 *C);
Add(Matr1 *A, Matr1 *B, Matr2 *C);

and a number of other combinations. This partially satisfies the design goal,
but one has to be very careful about casting the pointers prior to
calling Add(), in order to ensure that a suitable variant will be called.
Instead of writing

Add(A, B, C);

I would then have to write

Add( dynamic_cast<Matr1 *>(A),
dynamic_cast<Matr2 *>(B),
dynamic_cast<Matr3 *>(C) );
You don't need to do that there are better ways.
One problem is that I don't know how many different parameter
combinations you intend. Is it always going to be 2 or 3 parameters?
With 3 parameters max and say 16 different types of matrixes the
number of combinations becomes excessive for overloading.

What you might want to do is make all parameters bases:(phsuedo code)
Add(MatrBase*, MatrBase*);
now do you want to overload this to take a various numbers of
arguments?
Alternatively you could make it only take 2 arguments and nest
function calls, then you would need to return an object:
Add(Add(MatrBase*, MatrBase*), Add MatrBase*);
But this could be hidden inside a class so that you only call Add on 1
object.

Matr2Obj.Add( MatrBase*, MatrBase*)
with an implementation of:

Matr2::Add(MatrBase* arg1, MatrBase* arg2){
//done on two lines for clarity
tempobj =arg1->Add(arg2);
this->Add(tempObj);
}

so every Matrix class imlements and Add method which takes 1 parameter
and does its stuff accordingly then returns an object, which will be
of polymorphic type.

Matr& Matr::Add(Matr*){
//do add stuff
return *this;
//reutrn reference or pointer or temp obj?
//this depends on other aspects of your design.
//reference seems like a good candidate.
}

I think you should consider using a return value as most add
operations usually do, but you say you want to store result in a
particular object so this is also a grey area without understanding
more detail of your design.
which is obviously inconvenient, and also invalidates the idea
of polymorphism, as the run type of the objects must be known
at compile time. In addition any extension of the library becomes
difficult, because if I define a new matrix type, together with
new Add() variants needed for it, the new Add() functions will
not be known as friends of the respective classes in the library. I agree that type casting is not a godd way, also templates are ruled
out.
Extensibility should and could be preserved using the method I tried
to describe above using polyorphism.
Implementing Add() as a class method would also be difficult, because
there would have to be many overloaded variants in every class, the number
increasing with every new derived class.
Yes I see this, this rules out global and class methods.

A possible alternative might be to define one global function

void Add(Matr *A, Matr *B, Matr *C);

(or class method), and perform the run-time type checking within this
function. This would require having several if-else options within the
function. However this solution appears not very elegant, again somewhat
inconsistent with the idea of the polymorphism, and similarly hard to
extend on new matrix types (apart from being likely inefficient). RTTI doesn't sound like a good option.

In view of the above, my question is:

What else can be done, using C++ capabilities, to allow me
just to write

Add(A, B, C);

and at the same time be able to extend the library
without having to make changes in the library? I'm not sure if I explained properly because I have a few grey areas
about your design goals as described above, best I can see at present
is to create a pure virtual Add() which takes one parameter and
returns an object. Then dependant on your design goals overload this
to accept more parameters with each overloaded version calling the
virtual single parameter implementation of each parameter bar the last
with the argument of the parameter in the parameter list.
Then perhaps this can be improved upon to remove the returned object
for design goal and/or efficiency reasons.

Sincerely,

L.B.
*-------------------------------------------------------------------*
| Dr. Leslaw Bieniasz, |
| Institute of Physical Chemistry of the Polish Academy of Sciences,|
| Department of Electrochemical Oxidation of Gaseous Fuels, |
| ul. Zagrody 13, 30-318 Cracow, Poland. |
| tel./fax: +48 (12) 266-03-41 |
| E-mail: nb******@cyf-kr.edu.pl |
*-------------------------------------------------------------------*
| Interested in Computational Electrochemistry? |
| Visit my web site: http://www.cyf-kr.edu.pl/~nbbienia |
*-------------------------------------------------------------------*


To sum up my patchy explanations will something like this help:

class Matr{
virtual Matr& Add(Matr&)=0{return *this}
};

class Matr1:Matr{
virtual Matr& Add(Matr*);
virtual Matr& Add(Matr*, Matr*);
virtual Matr& Add(Matr*, Matr*, Matr*);
};

Matr& Matr1::Add(Matr* arg){
//do add stuff
return *this;
}

Matr& Matr1::Add(Matr* arg1, Matr* arg2){
this->Add(arg1->Add(arg2));
return *this;
}

Matr& Matr1::Add(Matr* arg1, Matr* arg2, Matr* arg3){
this->Add(arg1->Add(arg2->Add(arg3)));
}

usage:
Matr* mp_array[5] = {new Matr1, new Matr1, new Matr1, new Matr1,new
Matr1};

mp_array[0]->Add(mp_array[1],mp_array[2],mp_array[3]);

etc etc.
HTH
Paul.
Jul 22 '05 #2

P: n/a
[snip]
Just to add that obviously the previous explanation I posted would
need to overload Add() to accept a reference type parameter:
Matr& Add(Matr&);

In fact reference tpyes should be the base design and it should be
overloaded for pointers IMO.

Paul.
Jul 22 '05 #3

P: n/a


Cracow, 22.09.2004

Hello,
On Mon, 20 Sep 2004, Paul wrote:
Leslaw Bieniasz <nb******@cyf-kr.edu.pl> wrote in message news:<Pi*******************************@kinga.cyf-kr.edu.pl>...
I need to implement a library containing a hierarchy of classes
together with some binary operations on objects. To fix attention,
let me assume that it is a hierarchy of algebraic matrices with the
addition operation. Thus, I want to have a virtual base class

class Matr;

and some derived classes:

class Matr1 : public Matr;
class Matr2 : public Matr;
class Matr3 : public Matr2;

etc.

I want to be able to write the code like the following one, which
intensively uses polymorphism:

Matr* A = new Matr1();
Matr* B = new Matr2();
Matr* C = new Matr3();
.
.
.
Add(A, B, C);

[or C->Add(A,B); ]

where the addition is supposed to be implemented either as a global function
that adds A to B and places the result into C, or as a virtual method
of a base class, which calculates the sum of A and B, and puts the result
into the object which calls the method.
In the first case the function(s) would have to be friends of the Matr1,
Matr2, etc. class(es). The same could be done using overloaded operators +,
but this is a secondary issue for my question, see below. I also abstract,
at the moment, from how exactly the binary operation is realised (for
example, one might use, or not use, expression templates).
You should consider a static method as an alternative to the global
method:
Matr::Add(A,B,C);
I prefer this as it makes things clearer.

Then you could experiment with different implementations of a static
method in different derived classes:
Matr1::Add(...);
Matr2::Add(...);


--------------------------------
I don't see how this might be helpful, as this would require the
knowledge, at compile time, of which static function to call,
whereas my goal is that this issue be resolved at run time.
--------------------------------
The problem with the above is that each of the matrices
Matr1, Matr2, Matr3,... may require a different implementation
of the addition operation. For example, adding a diagonal matrix to a
dense matrix is possible, but is done differently from adding a dense
matrix to a dense matrix, or diagonal matrix to a diagonal matrix.
Thus, I may need to have a number of versions of Add():

Add(Matr1 *A, Matr1 *B, Matr1 *C);
Add(Matr2 *A, Matr2 *B, Matr2 *C);
Add(Matr3 *A, Matr3 *B, Matr3 *C);

but also

Add(Matr1 *A, Matr2 *B, Matr3 *C);
Add(Matr1 *A, Matr1 *B, Matr2 *C);

and a number of other combinations. This partially satisfies the design goal,
but one has to be very careful about casting the pointers prior to
calling Add(), in order to ensure that a suitable variant will be called.
Instead of writing

Add(A, B, C);

I would then have to write

Add( dynamic_cast<Matr1 *>(A),
dynamic_cast<Matr2 *>(B),
dynamic_cast<Matr3 *>(C) );

You don't need to do that there are better ways.
One problem is that I don't know how many different parameter
combinations you intend. Is it always going to be 2 or 3 parameters?
With 3 parameters max and say 16 different types of matrixes the
number of combinations becomes excessive for overloading.

What you might want to do is make all parameters bases:(phsuedo code)
Add(MatrBase*, MatrBase*);
now do you want to overload this to take a various numbers of
arguments?
Alternatively you could make it only take 2 arguments and nest
function calls, then you would need to return an object:
Add(Add(MatrBase*, MatrBase*), Add MatrBase*);
But this could be hidden inside a class so that you only call Add on 1
object.

Matr2Obj.Add( MatrBase*, MatrBase*)
with an implementation of:

Matr2::Add(MatrBase* arg1, MatrBase* arg2){
//done on two lines for clarity
tempobj =arg1->Add(arg2);
this->Add(tempObj);
}

so every Matrix class imlements and Add method which takes 1 parameter
and does its stuff accordingly then returns an object, which will be
of polymorphic type.

Matr& Matr::Add(Matr*){
//do add stuff
return *this;
//reutrn reference or pointer or temp obj?
//this depends on other aspects of your design.
//reference seems like a good candidate.
}

I think you should consider using a return value as most add
operations usually do, but you say you want to store result in a
particular object so this is also a grey area without understanding
more detail of your design.


-----------------------------------
No, my problem is not that I may have a variable number of addends
(although this issue presents another difficulty).
In the problem that I described, I always have two rvalue arguments and
one lvalue argument:

C = A + B

but I can have multiple types of A, B, and C, so that the entire
(binary) operation is polymorphic.
------------------------------------
which is obviously inconvenient, and also invalidates the idea
of polymorphism, as the run type of the objects must be known
at compile time. In addition any extension of the library becomes
difficult, because if I define a new matrix type, together with
new Add() variants needed for it, the new Add() functions will
not be known as friends of the respective classes in the library.

I agree that type casting is not a godd way, also templates are ruled
out.
Extensibility should and could be preserved using the method I tried
to describe above using polyorphism.
Implementing Add() as a class method would also be difficult, because
there would have to be many overloaded variants in every class, the number
increasing with every new derived class.


Yes I see this, this rules out global and class methods.


A possible alternative might be to define one global function

void Add(Matr *A, Matr *B, Matr *C);

(or class method), and perform the run-time type checking within this
function. This would require having several if-else options within the
function. However this solution appears not very elegant, again somewhat
inconsistent with the idea of the polymorphism, and similarly hard to
extend on new matrix types (apart from being likely inefficient).

RTTI doesn't sound like a good option.


In view of the above, my question is:

What else can be done, using C++ capabilities, to allow me
just to write

Add(A, B, C);

and at the same time be able to extend the library
without having to make changes in the library?

I'm not sure if I explained properly because I have a few grey areas
about your design goals as described above, best I can see at present
is to create a pure virtual Add() which takes one parameter and
returns an object. Then dependant on your design goals overload this
to accept more parameters with each overloaded version calling the
virtual single parameter implementation of each parameter bar the last
with the argument of the parameter in the parameter list.
Then perhaps this can be improved upon to remove the returned object
for design goal and/or efficiency reasons.

To sum up my patchy explanations will something like this help:

class Matr{
virtual Matr& Add(Matr&)=0{return *this}
};

class Matr1:Matr{
virtual Matr& Add(Matr*);
virtual Matr& Add(Matr*, Matr*);
virtual Matr& Add(Matr*, Matr*, Matr*);
};

Matr& Matr1::Add(Matr* arg){
//do add stuff
return *this;
}

Matr& Matr1::Add(Matr* arg1, Matr* arg2){
this->Add(arg1->Add(arg2));
return *this;
}

Matr& Matr1::Add(Matr* arg1, Matr* arg2, Matr* arg3){
this->Add(arg1->Add(arg2->Add(arg3)));
}

usage:
Matr* mp_array[5] = {new Matr1, new Matr1, new Matr1, new Matr1,new
Matr1};

mp_array[0]->Add(mp_array[1],mp_array[2],mp_array[3]);

etc etc.


-------------------------------------------
To express my problem more rigorously, I now think this is an example
of the "multiple dispatch" problem, as described by
Scott Meyers in "More Effective C++", Addison-wesley, 1996, p. 228.
Unfortunately, he focused on the remedies for the unary operation
case, such as

C = A

whereas I need binary (and multi-operand, as you indicated, operations).
I also think that his proposal may not be very efficient, as the
lookup procedure involves std::map searches, which I expect to be rather
slow.

Hence, I can reformulate my question: where can I find descriptions
of how to handle the multiple dispatch problem efficiently and
effectively in the case of binary and multi-operand operations.
Sincerely,

L.B.
*-------------------------------------------------------------------*
| Dr. Leslaw Bieniasz, |
| Institute of Physical Chemistry of the Polish Academy of Sciences,|
| Department of Electrochemical Oxidation of Gaseous Fuels, |
| ul. Zagrody 13, 30-318 Cracow, Poland. |
| tel./fax: +48 (12) 266-03-41 |
| E-mail: nb******@cyf-kr.edu.pl |
*-------------------------------------------------------------------*
| Interested in Computational Electrochemistry? |
| Visit my web site: http://www.cyf-kr.edu.pl/~nbbienia |
*-------------------------------------------------------------------*
Jul 22 '05 #4

P: n/a
Leslaw Bieniasz <nb******@cyf-kr.edu.pl> wrote in message news:<Pi******************************@kinga.cyf-kr.edu.pl>...
Cracow, 22.09.2004
[snip]
-----------------------------------
No, my problem is not that I may have a variable number of addends
(although this issue presents another difficulty).
In the problem that I described, I always have two rvalue arguments and
one lvalue argument:

C = A + B

but I can have multiple types of A, B, and C, so that the entire
(binary) operation is polymorphic.
------------------------------------


It seems like will need a method for all additon combinations.
for example if your library started of with 3 types of matrix say:
DenseMatrix
DiamondMatrix
HybridMatrix

Lets call them A,B and C.
Then your going to need a method for each to be able to add each of
the others and itself thus allowing for commutativity:
A::Add(A);
A::Add(B);
A::Add(C);
B::Add(A);
B::Add(B); etc etc..

So then you can do:
C=A+B; C=C+C; C=B+A; B=C+A etc etc for all combinations;

Then when you later wish to add a Matrix type ,call it D, you'll need
to include an Add method for each A,B and C:
D::Add(A);
D::Add(B); etc .

So now you can do D=A+B+C; D+B; D+A
But what if A,B or C tries to add D, you cannot do A=B+D because there
is no method:
A::Add(D) (doesn't exist)

But what A can do is call D::Add(A) if A knows about D's Base class
and Ds' Add method is virtual.
It also has to know that it doesn't know about D, here's how you could
do this:

create an enumerator:
enum Matrix_types{A,B,C}

class A{
Matrix_types myType;
int known_types;
A():known_types(3),myType(A){//constructor}
};

do the same for all other classes.
When you want to add D you extend the enumerator to include D:
enum Matrix_types{A,B,C,D}

D():known_types(4), myType(D){//constructor}
Now D knows of 4 types but A only knows about 3.

Now back to the problem of A calling D's Add method.
When A gets called to Add(D) it knows that it can only add the first 3
enumerated types(A,B and C) so what it does is call D::Add(A). D can
handle A's so this is no problem.

This is a design that will work but it basically requires a function
for every combination of two addable Marix_types and I'm still not
entirely sure this is what you want.

If some of the functionality is duplicated across Methods you can
reduce the size of your lib.

Here is a rough example how you could use an array of function
pointers in each class to point at the functions:

//Compilable code , hopefully.

enum types{T1,T2,T3};
/*for example T1=DenseMatr T2=DiamondMatr T3=HybridMatr*/

class Matr{
public:
Matr(){}
virtual ~Matr(){}
virtual Matr& _add(Matr*,types)=0{return *this;}
virtual Matr& Add(Matr*){return *this;}
virtual Matr& Add(Matr*, Matr*) {return *this;}
};

/*function declarations*/
Matr* T1addT1(Matr* p1, Matr* p2);
Matr* T1addT2(Matr* p1, Matr* p2);
Matr* T1addT3(Matr* p1, Matr* p2);

typedef Matr* (*FP)(Matr*,Matr*);
class DenseMatr:public Matr{
public:
FP fp[3];
/*lib contains 3 types of Matrix at time of writing */
/*so DenseMatrix need to be able to add 3 types including itself? */
int f_count;
types T;
DenseMatr():T(T1),f_count(2){
fp[0]= T1addT1;
fp[1]= T1addT2;
fp[2]= T1addT3;
/*obviously critical that the correct function is assigned to FP when
writing code*/
}
virtual ~DenseMatr(){}
Matr& _add(Matr* arg, types t){
if(t<f_count)
fp[t](this,arg);
/*if this class knows about arg type*/
else
arg->Add(this);
/*if arg type is an afterthought it should*/
/*provide add functionality for this type*/
return *this;
}
};

/*bare class*/
class DiamondMatr:public Matr{
public:
FP fp;
types T;
DiamondMatr():T(T2){}
virtual ~DiamondMatr(){}
Matr& _add(Matr* arg, types T){
return *this;}
};

/*bare class*/
class HybridMatr:public Matr{
public:
FP fp;
types T;
HybridMatr():T(T3){}
virtual ~HybridMatr(){}
Matr& _add(Matr* arg, types T){
return *this;}
};
class Matr1:public DenseMatr{
public:
Matr1(){}
~Matr1(){}
Matr1(const Matr1& rhs){}

Matr1& Add(Matr* arg){
arg->_add(this,T);
return *this;
}

Matr1& Add(Matr* p1, Matr* p2){
Add(&(p1->Add(p2)));
return *this;
}
};

Matr* T1addT1(Matr* p1, Matr* p2){
static_cast<DenseMatr*>(p1);
static_cast<DenseMatr*>(p2);
/*Do addition*/
return p1;
}

Matr* T1addT2(Matr* p1, Matr* p2){
static_cast<DenseMatr*>(p1);
static_cast<DiamondMatr*>(p2);
/*Do addition*/
return p1;
}
Matr* T1addT3(Matr* p1, Matr* p2){
static_cast<DenseMatr*>(p1);
static_cast<HybridMatr*>(p2);
/*Do addition*/
return p1;
}
int main(int argc, char *argv[])
{
Matr* m01 = new Matr1;
Matr* m02 = new Matr1;

m01->Add(m02,m01);

delete m01;
delete m02;
return 0;
}


I know it's rough but it's a bit complicated and needs a fair bit of
work to be made complete.
which is obviously inconvenient, and also invalidates the idea
of polymorphism, as the run type of the objects must be known
at compile time. In addition any extension of the library becomes
difficult, because if I define a new matrix type, together with
new Add() variants needed for it, the new Add() functions will
not be known as friends of the respective classes in the library.

I agree that type casting is not a godd way, also templates are ruled
out.
Extensibility should and could be preserved using the method I tried
to describe above using polyorphism.
Implementing Add() as a class method would also be difficult, because
there would have to be many overloaded variants in every class, the number
increasing with every new derived class.


Yes I see this, this rules out global and class methods.


A possible alternative might be to define one global function

void Add(Matr *A, Matr *B, Matr *C);

(or class method), and perform the run-time type checking within this
function. This would require having several if-else options within the
function. However this solution appears not very elegant, again somewhat
inconsistent with the idea of the polymorphism, and similarly hard to
extend on new matrix types (apart from being likely inefficient). RTTI doesn't sound like a good option.

In view of the above, my question is:

What else can be done, using C++ capabilities, to allow me
just to write

Add(A, B, C);

and at the same time be able to extend the library
without having to make changes in the library?

I'm not sure if I explained properly because I have a few grey areas
about your design goals as described above, best I can see at present
is to create a pure virtual Add() which takes one parameter and
returns an object. Then dependant on your design goals overload this
to accept more parameters with each overloaded version calling the
virtual single parameter implementation of each parameter bar the last
with the argument of the parameter in the parameter list.
Then perhaps this can be improved upon to remove the returned object
for design goal and/or efficiency reasons.

To sum up my patchy explanations will something like this help:

class Matr{
virtual Matr& Add(Matr&)=0{return *this}
};

class Matr1:Matr{
virtual Matr& Add(Matr*);
virtual Matr& Add(Matr*, Matr*);
virtual Matr& Add(Matr*, Matr*, Matr*);
};

Matr& Matr1::Add(Matr* arg){
//do add stuff
return *this;
}

Matr& Matr1::Add(Matr* arg1, Matr* arg2){
this->Add(arg1->Add(arg2));
return *this;
}

Matr& Matr1::Add(Matr* arg1, Matr* arg2, Matr* arg3){
this->Add(arg1->Add(arg2->Add(arg3)));
}

usage:
Matr* mp_array[5] = {new Matr1, new Matr1, new Matr1, new Matr1,new
Matr1};

mp_array[0]->Add(mp_array[1],mp_array[2],mp_array[3]);

etc etc.


-------------------------------------------
To express my problem more rigorously, I now think this is an example
of the "multiple dispatch" problem, as described by
Scott Meyers in "More Effective C++", Addison-wesley, 1996, p. 228.
Unfortunately, he focused on the remedies for the unary operation
case, such as

C = A

whereas I need binary (and multi-operand, as you indicated, operations).
I also think that his proposal may not be very efficient, as the
lookup procedure involves std::map searches, which I expect to be rather
slow.

Hence, I can reformulate my question: where can I find descriptions
of how to handle the multiple dispatch problem efficiently and
effectively in the case of binary and multi-operand operations.
Sincerely,

L.B.
*-------------------------------------------------------------------*
| Dr. Leslaw Bieniasz, |
| Institute of Physical Chemistry of the Polish Academy of Sciences,|
| Department of Electrochemical Oxidation of Gaseous Fuels, |
| ul. Zagrody 13, 30-318 Cracow, Poland. |
| tel./fax: +48 (12) 266-03-41 |
| E-mail: nb******@cyf-kr.edu.pl |
*-------------------------------------------------------------------*
| Interested in Computational Electrochemistry? |
| Visit my web site: http://www.cyf-kr.edu.pl/~nbbienia |
*-------------------------------------------------------------------*


I have never read the book you mention above. Sorry can't help you
there.
Paul.
Jul 22 '05 #5

This discussion thread is closed

Replies have been disabled for this discussion.