double dispatch example

J

j j

Hi

I'm trying to understand the double dispatch mechanism. Below is my
simple program.
Please let me know whether this is good double dispatch example. In
case
it is missing something please let me know what is it.

thanks in advance

#include <iostream>

using namespace std;

class X;

class A
{
public:
virtual void fun(X &x)
{
cout << "A::fun" << endl;
}
};

class B : public A
{
public:
void fun(X &x)
{
cout << "B::fun" << endl;
}
};

class X
{
public:
virtual void fun(A &a)
{
cout << "X::fun" << endl;
a.fun(*this); // second dispatch
}
};

class Y : public X
{
public:
void fun(A &a)
{
cout << "Y::fun" << endl;
a.fun(*this); // second dispatch
}
};

int main(void)
{
B b;

Y y;
X &x = y;
x.fun(b); // first dispatch

return 0;
}
 
P

Pavel

j said:
Hi

I'm trying to understand the double dispatch mechanism. Below is my
simple program.
Please let me know whether this is good double dispatch example. In
case
it is missing something please let me know what is it.

thanks in advance

#include<iostream>

using namespace std;

class X;

class A
{
public:
virtual void fun(X&x)
{
cout<< "A::fun"<< endl;
}
};

class B : public A
{
public:
void fun(X&x)
{
cout<< "B::fun"<< endl;
}
};

class X
{
public:
virtual void fun(A&a)
{
cout<< "X::fun"<< endl;
a.fun(*this); // second dispatch
}
};

class Y : public X
{
public:
void fun(A&a)
{
cout<< "Y::fun"<< endl;
a.fun(*this); // second dispatch
}
};

int main(void)
{
B b;

Y y;
X&x = y;
x.fun(b); // first dispatch

return 0;
}
It is a moot question. C++ does not have internal support for multiple dispatch
(common lisp does).

Your example is, however, by and large what some people call "Double-dispatch"
in C++ -- but I do not think it is correct. It is funny how Wikipedia article on
"Double dispatch" at http://en.wikipedia.org/wiki/Double_dispatch essentially
repeats your design whereas the article on multiple dispatch at
http://en.wikipedia.org/wiki/Multiple_dispatch rightly explains that C++ only
supports single dispatch directly (Common Lisp supports multiple dynamic
dispatch) and illustrates how you could "do it yourself" in Java, C and C++.
Their first example in C++ is analogous to that in Java and both are quite lame.
Their second example in C++ is better but still has serious problems I will stop
at later. Their best example is IMHO that is given for C language (it could be
of course used in C++, too, even with few additional perks).

More specifically, my feeling is that your design suffers from two fundamental
issues:

1. Incorrect encapsulation target (Wikipedia C++ examples suffer from same).
With multiple (in particular, double) dispatch, the behavior of a combination of
several dynamic types does not belong to either of them. It should be
encapsulated in an separate entity (that is sometimes called MultiMethod).

2. An arbitrary choice of having 2 class hierarchies (did you feel you needed 2
hierarchies as you wanted double-dispatch? You are not alone. The authors of
Wikipedia's "Double Dispatch" article apparently felt same).

As usual, a litmus test for the design quality is to change the user
requirements. Try to satisfy this simple set of requirements with your design:

- when X is having fun with A, the required behavior is to print "ABC"
- when X is having fun with B, the required behavior is to print "DEF"
- when Y is having fun with A *or* B, the required behavior is to print "GHI"

Below is my version of the solution:

------------cut here-----------
#include <iostream>
#include <typeinfo>
#include <vector>

using namespace std;

template <class T>
class FunTicketHolder {
public:
static int ticket() { return ticket_; }
static void ticket(int t) { ticket_ = t; }
private:
static int ticket_;
};

template<class T>
int FunTicketHolder<T>::ticket_ = -1;

class FunLover {
public:
virtual int funClassRegId() const = 0;
};

ostream&
operator<<(ostream &os, const FunLover &x) {
os << typeid(x).name() << '(' << (const void*)&x << ')';
}

class A: public FunLover, public FunTicketHolder<A> {
virtual int funClassRegId() const { return ticket(); }
};

class B: public FunLover, public FunTicketHolder<B> {
virtual int funClassRegId() const { return ticket(); }
};

class X: public FunLover, public FunTicketHolder<X> {
virtual int funClassRegId() const { return ticket(); }
};

class Y: public FunLover, public FunTicketHolder<Y> {
virtual int funClassRegId() const { return ticket(); }
};

class DoubleDispatchedFun {
typedef void (*FunFuncPt)(FunLover &, FunLover &);
public:
void operator()(FunLover &x, FunLover &y) {
cout << "Fun lovers " << x << " and " << y << " are ";
if(!areRegisteredForFun(x, y)) {
cout << "missing all the fun:\nThey forgot to register!" << endl;
return;
}
cout << "having their fun:\n";
funRegistry_[x.funClassRegId()][y.funClassRegId()](x, y);
}
template <class X, class Y>
void registerForFun(FunFuncPt fun) {
int xIdx = checkInFunLoverClass<X>();
int yIdx = checkInFunLoverClass<Y>();
letAnyFunLoverBeFirst(xIdx, yIdx);
FunFuncPt oldFun = funRegistry_[xIdx][yIdx];
if (!fun)
if(oldFun)
cout << "Changed your mind? No problem!" << endl;
else
cout << "You are not funny!" << endl;
else
if(oldFun)
if(oldFun == fun)
cout << "You've got it already registered" << endl;
else
cout << "You want it another way? No problem!" << endl;
else
cout << "Welcome to fun-lover club, " << typeid(X).name() <<
" and " << typeid(Y).name() << '!' << endl;
funRegistry_[xIdx][yIdx] = fun;
funRegistry_[yIdx][xIdx] = fun;
}
private:
bool areRegisteredForFun(FunLover &x, FunLover &y) {
return x.funClassRegId() >= 0 &&
x.funClassRegId() < funRegistry_.size() &&
y.funClassRegId() >= 0 &&
y.funClassRegId() < funRegistry_[x.funClassRegId()].size() &&
funRegistry_[x.funClassRegId()][y.funClassRegId()] != 0;
}
template <class X>
int checkInFunLoverClass() {
if(X::ticket() >= 0)
return X::ticket();
X::ticket(funRegistry_.size());
funRegistry_.resize(X::ticket() + 1);
return X::ticket();
}
void letAnyFunLoverBeFirst(int xIdx, int yIdx) {
letFirstBeFirst(xIdx, yIdx);
letFirstBeFirst(yIdx, xIdx);
}
void letFirstBeFirst(int firstIdx, int secondIdx) {
int sizeAtFirstIdx = funRegistry_[firstIdx].size();
if(secondIdx >= sizeAtFirstIdx)
funRegistry_[firstIdx].resize(secondIdx + 1);
}
vector<vector<FunFuncPt> > funRegistry_;
};

void
printABC(FunLover &, FunLover &) {
cout << "ABC" << endl; // fun shall be flushed!
}
void
printDEF(FunLover &, FunLover &) {
cout << "DEF" << endl; // fun shall be flushed!
}
void
printGHI(FunLover &, FunLover &) {
cout << "GHI" << endl; // fun shall be flushed!
}

int
main(int, char*[])
{
DoubleDispatchedFun ddFun;

// register double-dispatchable classes; this is
// separated so that you could register fun loving
// classes dynamically, in different modules
ddFun.registerForFun<X, A>(printABC);
ddFun.registerForFun<X, B>(printDEF);
ddFun.registerForFun<Y, A>(printGHI);
ddFun.registerForFun<Y, B>(printGHI);

// all these individuals...
A realA;
B realB;
X realX;
Y realY;

// are fun-lovers
FunLover &x = realX;
FunLover &y = realY;
FunLover &a = realA;
FunLover &b = realB;

// let's the fun begin!
ddFun(x, a);
ddFun(x, b);
ddFun(y, a);
ddFun(y, b);
ddFun(b, y); // just for the heck of it, let's change positions..
ddFun(x, y); // We do not mind x's having fun with y..
ddFun(x, x); // or itself.. as long as it is a registered fun.

return 0;
}

------------cut here-----------


-Pavel
 

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,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top