double dispatch example

Discussion in 'C++' started by j j, Jun 9, 2012.

  1. j j

    j j Guest

    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;
    }
    j j, Jun 9, 2012
    #1
    1. Advertising

  2. j j

    Pavel Guest

    j j wrote:
    > 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
    Pavel, Jun 9, 2012
    #2
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Lorenzo Bettini
    Replies:
    0
    Views:
    363
    Lorenzo Bettini
    Jul 26, 2003
  2. Sydex
    Replies:
    12
    Views:
    6,453
    Victor Bazarov
    Feb 17, 2005
  3. Thomas Matthews
    Replies:
    1
    Views:
    878
    Victor Bazarov
    Feb 21, 2005
  4. Replies:
    0
    Views:
    590
  5. AndyL
    Replies:
    1
    Views:
    297
    bruno at modulix
    Feb 16, 2006
Loading...

Share This Page