"virtual pair"

D

Denis Remezov

Valery said:
hi All,

how to make a member function, which is virtual
not for a single object, but for the *pair* of objects?..

Here goes the skeleton of the code, which should
ideally print "ABCD":
------------------------------------
class A {};
struct A1 : public A {} a1;
struct A2 : public A {} a2;

struct S {};
struct S1 : public S {} s1;
struct S2 : public S {} s2;

void go(const A* pa, const S* sa) {
// ??
}

int main() {

go(&a1, &s1); // => "A"
go(&a1, &s2); // => "B"
go(&a2, &s1); // => "C"
go(&a2, &s2); // => "D"

return 0;
}
------------------------------------
But the question is how to obtain this without
dynamic_casting and accessing type info.
i.e. obtain just using usual "virtual" member
functions in C++.

thanks,
Valery

They usually call it "double dispatch". Try googling for "double dispatch",
"multi-methods" and "visitor pattern". Since the language provides no direct
support, you will have to imitate it (curiously, there is a discussion of
some "whys" in the "The Design and Evolution of C++" [Stroustrup]).

The Visitor Pattern would be my first choice to consider to implement
this. Note that it is asymmetrical in regards to the roles of A and S.
It has variations.

In the trivial illustration below, class A is a "visitor".

#include <iostream>

using namespace std;

struct S1;
struct S2;

struct A {
virtual void go1(S1&)=0;
virtual void go2(S2&)=0;
};

struct A1 : A {
void go1(S1& s) {
cout<<"A"<<endl;
}
void go2(S2& s) {
cout<<"B"<<endl;
}
};
struct A2 : A {
void go1(S1& s) {
cout<<"C"<<endl;
}
void go2(S2& s) {
cout<<"D"<<endl;
}
};

struct S {
virtual void go(A&) = 0;
};

struct S1 : S {
void go(A& a) {
a.go1(*this);
}
};

struct S2 : S {
void go(A& a) {
a.go2(*this);
}
};

int main() {
A1 a1;
A2 a2;
S1 s1;
S2 s2;

s1.go(a1);
s2.go(a1);
s1.go(a2);
s2.go(a2);

return 0;
}

Denis
 
D

Denis Remezov

Alf P. Steinbach said:
Double urgh, you can (it's just that visitor pattern _usually_ involved RTTI).
Seems I'm a bit too tired to give advice. Coffee!

All right :)
I've come across alternative VP schemes (well, at least /one/ alternative scheme)
that did not use RTTI either, but I am not prepared to vouch for them at the
moment.

Denis
 
V

Valery

hi All,

how to make a member function, which is virtual
not for a single object, but for the *pair* of objects?..

Here goes the skeleton of the code, which should
ideally print "ABCD":
------------------------------------
class A {};
struct A1 : public A {} a1;
struct A2 : public A {} a2;

struct S {};
struct S1 : public S {} s1;
struct S2 : public S {} s2;

void go(const A* pa, const S* sa) {
// ??
}

int main() {

go(&a1, &s1); // => "A"
go(&a1, &s2); // => "B"
go(&a2, &s1); // => "C"
go(&a2, &s2); // => "D"

return 0;
}
------------------------------------
But the question is how to obtain this without
dynamic_casting and accessing type info.
i.e. obtain just using usual "virtual" member
functions in C++.

thanks,
Valery
 
L

Leor Zolman

hi All,

how to make a member function, which is virtual
not for a single object, but for the *pair* of objects?..

What you want is a technique known as "double dispatch", or "multiple
dispatch". An excellent summary of various approaches to the technique may
be found as Item 31 in Scott Meyers' _More Effective C++_.

Some interesting on-line resources:
http://www.eptacom.net/pubblicazioni/pub_eng/mdisp.html

http://c2.com/cgi/wiki?DoubleDispatchExample

http://homepage.ntlworld.com/w.weston/multiple_dispatch.html

HTH,
-leor
 
A

Alf P. Steinbach

* (e-mail address removed) (Valery) schriebt:
how to make a member function, which is virtual
not for a single object, but for the *pair* of objects?..

Here goes the skeleton of the code, which should
ideally print "ABCD":
------------------------------------
class A {};
struct A1 : public A {} a1;
struct A2 : public A {} a2;

struct S {};
struct S1 : public S {} s1;
struct S2 : public S {} s2;

void go(const A* pa, const S* sa) {
// ??
}

int main() {

go(&a1, &s1); // => "A"
go(&a1, &s2); // => "B"
go(&a2, &s1); // => "C"
go(&a2, &s2); // => "D"

return 0;
}
------------------------------------
But the question is how to obtain this without
dynamic_casting and accessing type info.
i.e. obtain just using usual "virtual" member
functions in C++.

There is a potentially unbounded number N of classes derived from A, and a
potentially unbounded number M of class derived from S. The number of
different results/functions is potentially N*M. Different techniques are
appropriate depending on the boundedness and the size of N*M, and depending on
how tightly coupled or not you want the classes to be.

If it is a requirement to avoid RTTI, that is, that you use only compile time
type information, then you will have tightly coupled classes, and this works
only for a very small, bounded number of classes, a "frozen" design.

In that case you can do it as follows (off the cuff) -- not that I recommend
this technique (perhaps you should relax your requirements a bit?):


#include <string>
#include <iostream>

class A {};
struct A1 : A {} a1;
struct A2 : A {} a2;

struct S {};
struct S1 : S {} s1;
struct S2 : S {} s2;

template< class A_, class S_ >
std::string g( A_ const&, S_ const& );

template<> std::string g<A1, S1>( A1 const&, S1 const& ) { return "A"; }
template<> std::string g<A1, S2>( A1 const&, S1 const& ) { return "B"; }
template<> std::string g<A1, S1>( A2 const&, S1 const& ) { return "C"; }
template<> std::string g<A1, S1>( A2 const&, S2 const& ) { return "D"; }

int main()
{
std::cout << ::g( a1, s1 ); // => "A"
std::cout << ::g( a1, s2 ); // => "B"
std::cout << ::g( a2, s1 ); // => "C"
std::cout << ::g( a2, s2 ); // => "D"
std::cout << std::endl;
}
 
A

Alf P. Steinbach

* (e-mail address removed) (Alf P. Steinbach) schriebt:
* (e-mail address removed) (Valery) schriebt:

There is a potentially unbounded number N of classes derived from A, and a
potentially unbounded number M of class derived from S. The number of
different results/functions is potentially N*M. Different techniques are
appropriate depending on the boundedness and the size of N*M, and depending on
how tightly coupled or not you want the classes to be.

If it is a requirement to avoid RTTI, that is, that you use only compile time
type information, then you will have tightly coupled classes, and this works
only for a very small, bounded number of classes, a "frozen" design.

In that case you can do it as follows (off the cuff) -- not that I recommend
this technique (perhaps you should relax your requirements a bit?):


#include <string>
#include <iostream>

class A {};
struct A1 : A {} a1;
struct A2 : A {} a2;

struct S {};
struct S1 : S {} s1;
struct S2 : S {} s2;

template< class A_, class S_ >
std::string g( A_ const&, S_ const& );

template<> std::string g<A1, S1>( A1 const&, S1 const& ) { return "A"; }
template<> std::string g<A1, S2>( A1 const&, S1 const& ) { return "B"; }
template<> std::string g<A1, S1>( A2 const&, S1 const& ) { return "C"; }
template<> std::string g<A1, S1>( A2 const&, S2 const& ) { return "D"; }

int main()
{
std::cout << ::g( a1, s1 ); // => "A"
std::cout << ::g( a1, s2 ); // => "B"
std::cout << ::g( a2, s1 ); // => "C"
std::cout << ::g( a2, s2 ); // => "D"
std::cout << std::endl;
}

Urgh. I made a few typos and templating isn't needed at all, just ordinary
function overloading. But I guess you see the main idea; and beware that
double dispatch mentioned by others does involve use of RTTI, i.e. isn't an
answer to your question; and no, you can't do it with virtual functions only.
 
A

Alf P. Steinbach

* (e-mail address removed) (Alf P. Steinbach) schriebt:
* (e-mail address removed) (Alf P. Steinbach) schriebt:

Urgh. I made a few typos and templating isn't needed at all, just ordinary
function overloading. But I guess you see the main idea; and beware that
double dispatch mentioned by others does involve use of RTTI, i.e. isn't an
answer to your question; and no, you can't do it with virtual functions only.

Double urgh, you can (it's just that visitor pattern _usually_ involved RTTI).
Seems I'm a bit too tired to give advice. Coffee!
 
D

Denis Remezov

Here is a simple example of a symmetrical non-commutative case
thrown in just for the sake of it. Depending on the usage, the number
of virtual calls can be even less than two.


#include <iostream>
using namespace std;

struct SA;
struct SB;

struct S {
virtual void go(S&) = 0;
virtual void go(SA&) = 0;
virtual void go(SB&) = 0;
};

struct SA : S {
void go(S& s) {
s.go(*this);
}
void go(SA& s) {
cout<<"A-A"<<endl;
}
void go(SB& s) {
cout<<"A-B"<<endl;
}
};

struct SB : S {
void go(S& s) {
s.go(*this);
}
void go(SA& s) {
cout<<"B-A"<<endl;
}
void go(SB& s) {
cout<<"B-B"<<endl;
}
};

void go(S& s0, S& s1) {
s0.go(s1);
}

int main() {
SA sa;
SB sb;

sa.go(sa);
sa.go(sb);
sb.go(sa);
sb.go(sb);

go(sa, sa);
go(sa, sb);
go(sb, sa);
go(sb, sb);

return 0;
}

Denis
 
V

Valery

Hi All,

thank you all for answers, they were very helpful.

best regards,
Valery.

Valery wrote in message
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,994
Messages
2,570,223
Members
46,814
Latest member
SpicetreeDigital

Latest Threads

Top