Avoid nested "if's"?

Discussion in 'C++' started by desktop, May 10, 2007.

  1. desktop

    desktop Guest

    I have 3 types of objects: bob1, bob2 and bob3. Each object is
    identified by a unique ID which gets returned by the function getId().

    All bobs are descendants from class BaseBob which is an abstract class
    that has the virtual function getId().

    I then have a function that prints a special string when a bob meets
    another bob (all combinations of bobs has to meet and since the are
    schizophrenic they can also meet themselves):

    void meet_bob(BaseBob b1, BaseBob b2){

    if (b1.getType == 1 && b2.getType == 1)
    {
    cout << "bob1 says hi to bob1" << endl;
    }
    else if (b1.getType == 1 && b2.getType == 2)
    {
    cout << "bob1 says hi to bob2" << endl;
    }
    else if (b1.getType == 1 && b2.getType == 3)
    {
    cout << "bob1 says hi to bob3" << endl;
    }
    else if (b1.getType == 2 && b2.getType == 2)
    {
    cout << "bob2 says hi to bob2" << endl;
    }
    else if (b1.getType == 2 && b2.getType == 3)
    {
    cout << "bob2 says hi to bob3" << endl;
    }
    ...
    ...

    }

    This is not pretty. If I want to use a switch instead I still have to
    make a switch for each case in the outer switch and then I have a design
    with nested switches instead (which is even uglier).

    Is there some better way to treat this kind of situation or is the use
    of nested switches the best solution?
    desktop, May 10, 2007
    #1
    1. Advertising

  2. desktop

    Lionel B Guest

    On Thu, 10 May 2007 12:15:15 +0200, desktop wrote:

    > I have 3 types of objects: bob1, bob2 and bob3. Each object is
    > identified by a unique ID which gets returned by the function getId().
    >
    > All bobs are descendants from class BaseBob which is an abstract class
    > that has the virtual function getId().


    Um... getId() doesn't appear in your code...

    > I then have a function that prints a special string when a bob meets
    > another bob (all combinations of bobs has to meet and since the are
    > schizophrenic they can also meet themselves):
    >
    > void meet_bob(BaseBob b1, BaseBob b2){


    If BaseBob is abstract, you can't pass it by value, since
    you can't instantiate it.

    > if (b1.getType == 1 && b2.getType == 1) {
    > cout << "bob1 says hi to bob1" << endl;
    > }
    > else if (b1.getType == 1 && b2.getType == 2) {
    > cout << "bob1 says hi to bob2" << endl;
    > }
    > else if (b1.getType == 1 && b2.getType == 3) {
    > cout << "bob1 says hi to bob3" << endl;
    > }
    > else if (b1.getType == 2 && b2.getType == 2) {
    > cout << "bob2 says hi to bob2" << endl;
    > }
    > else if (b1.getType == 2 && b2.getType == 3) {
    > cout << "bob2 says hi to bob3" << endl;
    > }
    > ...
    > ...
    >
    > }


    I'm guessing at what you want, but how about something like:

    void meet_bob(BaseBob* pb1, BaseBob* pb2){

    cout << "bob" << pb1->getId() << " says hi to bob" << pb2->getId() << endl;

    ...

    }

    assuming a sensible operator << for the return type of getId().

    --
    Lionel B
    Lionel B, May 10, 2007
    #2
    1. Advertising

  3. On 10 Maj, 12:38, Lionel B <> wrote:
    > On Thu, 10 May 2007 12:15:15 +0200, desktop wrote:
    > > I have 3 types of objects: bob1, bob2 and bob3. Each object is
    > > identified by a unique ID which gets returned by the function getId().

    >
    > > All bobs are descendants from class BaseBob which is an abstract class
    > > that has the virtual function getId().

    >
    > Um... getId() doesn't appear in your code...
    >
    > > I then have a function that prints a special string when a bob meets
    > > another bob (all combinations of bobs has to meet and since the are
    > > schizophrenic they can also meet themselves):

    >
    > > void meet_bob(BaseBob b1, BaseBob b2){

    >
    > If BaseBob is abstract, you can't pass it by value, since
    > you can't instantiate it.
    >
    >
    >
    > > if (b1.getType == 1 && b2.getType == 1) {
    > > cout << "bob1 says hi to bob1" << endl;
    > > }
    > > else if (b1.getType == 1 && b2.getType == 2) {
    > > cout << "bob1 says hi to bob2" << endl;
    > > }
    > > else if (b1.getType == 1 && b2.getType == 3) {
    > > cout << "bob1 says hi to bob3" << endl;
    > > }
    > > else if (b1.getType == 2 && b2.getType == 2) {
    > > cout << "bob2 says hi to bob2" << endl;
    > > }
    > > else if (b1.getType == 2 && b2.getType == 3) {
    > > cout << "bob2 says hi to bob3" << endl;
    > > }
    > > ...
    > > ...

    >
    > > }

    >
    > I'm guessing at what you want, but how about something like:
    >
    > void meet_bob(BaseBob* pb1, BaseBob* pb2){


    void meet_bob(const BaseBob& b1, const BaseBob& b2) {

    > cout << "bob" << pb1->getId() << " says hi to bob" << pb2->getId() << endl;


    cout << "bob" << b1.getType() << " says hi to bob" << b2.getType()
    << end;

    --
    Erik Wikström
    =?iso-8859-1?q?Erik_Wikstr=F6m?=, May 10, 2007
    #3
  4. desktop

    desktop Guest

    Lionel B wrote:
    > On Thu, 10 May 2007 12:15:15 +0200, desktop wrote:
    >
    >> I have 3 types of objects: bob1, bob2 and bob3. Each object is
    >> identified by a unique ID which gets returned by the function getId().
    >>
    >> All bobs are descendants from class BaseBob which is an abstract class
    >> that has the virtual function getId().

    >
    > Um... getId() doesn't appear in your code...
    >
    >> I then have a function that prints a special string when a bob meets
    >> another bob (all combinations of bobs has to meet and since the are
    >> schizophrenic they can also meet themselves):
    >>
    >> void meet_bob(BaseBob b1, BaseBob b2){

    >
    > If BaseBob is abstract, you can't pass it by value, since
    > you can't instantiate it.
    >
    >> if (b1.getType == 1 && b2.getType == 1) {
    >> cout << "bob1 says hi to bob1" << endl;
    >> }
    >> else if (b1.getType == 1 && b2.getType == 2) {
    >> cout << "bob1 says hi to bob2" << endl;
    >> }
    >> else if (b1.getType == 1 && b2.getType == 3) {
    >> cout << "bob1 says hi to bob3" << endl;
    >> }
    >> else if (b1.getType == 2 && b2.getType == 2) {
    >> cout << "bob2 says hi to bob2" << endl;
    >> }
    >> else if (b1.getType == 2 && b2.getType == 3) {
    >> cout << "bob2 says hi to bob3" << endl;
    >> }
    >> ...
    >> ...
    >>
    >> }

    >
    > I'm guessing at what you want, but how about something like:
    >
    > void meet_bob(BaseBob* pb1, BaseBob* pb2){
    >
    > cout << "bob" << pb1->getId() << " says hi to bob" << pb2->getId() << endl;
    >
    > ...
    >
    > }
    >
    > assuming a sensible operator << for the return type of getId().
    >


    Well I was more looking for at general way to optimize a large number of
    nested "if's" or "switches". In each case I might want to do something
    more complex that print a string in the future.

    Are there alternatives to nested switches when dealing with a large
    number of combinations? I was thinking about a tree structure but am nit
    sure if that is supported in c++.
    desktop, May 10, 2007
    #4
  5. desktop

    kingfox Guest

    On 5ÔÂ10ÈÕ, ÏÂÎç7ʱ04·Ö, desktop <> wrote:
    > Well I was more looking for at general way to optimize a large number of
    > nested "if's" or "switches". In each case I might want to do something
    > more complex that print a string in the future.
    >
    > Are there alternatives to nested switches when dealing with a large
    > number of combinations? I was thinking about a tree structure but am nit
    > sure if that is supported in c++.


    Well, I think there isn't a general way to resolve all the nested "if"
    or "switch". But in many situation, polymorphism may be a choice.
    kingfox, May 10, 2007
    #5
  6. On May 10, 12:15 pm, desktop <> wrote:
    > I have 3 types of objects: bob1, bob2 and bob3. Each object is
    > identified by a unique ID which gets returned by the function getId().
    >
    > All bobs are descendants from class BaseBob which is an abstract class
    > that has the virtual function getId().
    >
    > I then have a function that prints a special string when a bob meets
    > another bob (all combinations of bobs has to meet and since the are
    > schizophrenic they can also meet themselves):
    >
    > void meet_bob(BaseBob b1, BaseBob b2){
    >
    > if (b1.getType == 1 && b2.getType == 1)
    > {
    > cout << "bob1 says hi to bob1" << endl;
    > }
    > else if (b1.getType == 1 && b2.getType == 2)
    > {
    > cout << "bob1 says hi to bob2" << endl;
    > }
    > else if (b1.getType == 1 && b2.getType == 3)
    > {
    > cout << "bob1 says hi to bob3" << endl;
    > }
    > else if (b1.getType == 2 && b2.getType == 2)
    > {
    > cout << "bob2 says hi to bob2" << endl;
    > }
    > else if (b1.getType == 2 && b2.getType == 3)
    > {
    > cout << "bob2 says hi to bob3" << endl;
    > }
    > ...
    > ...
    >
    > }
    >
    > This is not pretty. If I want to use a switch instead I still have to
    > make a switch for each case in the outer switch and then I have a design
    > with nested switches instead (which is even uglier).
    >
    > Is there some better way to treat this kind of situation or is the use
    > of nested switches the best solution?


    Yes. There is technique called double dispatch which does not use
    switch or ifs. Somebody asked when is polimorphysm appropriate.
    Whenever you have case that you have actions based on types,
    is sure sign to use virtual functions.
    Simple implementation of your problem is here:

    #include <iostream>
    #include <ostream>
    using namespace std;

    class Bob1;
    class Bob2;

    class BaseBob{
    public:
    virtual void accept(BaseBob*)=0;
    virtual void visit(Bob1*)=0;
    virtual void visit(Bob2*)=0;
    // you can add Bob3
    virtual ~BaseBob(){}
    };

    class Bob1: public BaseBob{
    public:
    virtual void accept(BaseBob* b)
    {
    b->visit(this);
    }
    virtual void visit(Bob1* b)
    {
    cout<<"Bob1 says hi to Bob1\n";
    }
    virtual void visit(Bob2* b)
    {
    cout<<"Bob1 says hi to Bob2\n";
    }
    };

    class Bob2: public BaseBob{
    public:
    virtual void accept(BaseBob* b)
    {
    b->visit(this);
    }
    virtual void visit(Bob1* b)
    {
    cout<<"Bob2 says hi to Bob1\n";
    }
    virtual void visit(Bob2* b)
    {
    cout<<"Bob2 says hi to Bob2\n";
    }
    };

    int main()
    {
    Bob1 b1;
    Bob2 b2;
    b1.accept(&b1);
    b1.accept(&b2);
    b2.accept(&b1);
    b2.accept(&b2);
    return 0;
    }

    Greetings, Branimir.
    Branimir Maksimovic, May 10, 2007
    #6
  7. desktop

    anon Guest

    Branimir Maksimovic wrote:
    > On May 10, 12:15 pm, desktop <> wrote:
    >> I have 3 types of objects: bob1, bob2 and bob3. Each object is
    >> identified by a unique ID which gets returned by the function getId().
    >>
    >> All bobs are descendants from class BaseBob which is an abstract class
    >> that has the virtual function getId().
    >>
    >> I then have a function that prints a special string when a bob meets
    >> another bob (all combinations of bobs has to meet and since the are
    >> schizophrenic they can also meet themselves):
    >>
    >> void meet_bob(BaseBob b1, BaseBob b2){
    >>
    >> if (b1.getType == 1 && b2.getType == 1)
    >> {
    >> cout << "bob1 says hi to bob1" << endl;
    >> }
    >> else if (b1.getType == 1 && b2.getType == 2)
    >> {
    >> cout << "bob1 says hi to bob2" << endl;
    >> }
    >> else if (b1.getType == 1 && b2.getType == 3)
    >> {
    >> cout << "bob1 says hi to bob3" << endl;
    >> }
    >> else if (b1.getType == 2 && b2.getType == 2)
    >> {
    >> cout << "bob2 says hi to bob2" << endl;
    >> }
    >> else if (b1.getType == 2 && b2.getType == 3)
    >> {
    >> cout << "bob2 says hi to bob3" << endl;
    >> }
    >> ...
    >> ...
    >>
    >> }
    >>
    >> This is not pretty. If I want to use a switch instead I still have to
    >> make a switch for each case in the outer switch and then I have a design
    >> with nested switches instead (which is even uglier).
    >>
    >> Is there some better way to treat this kind of situation or is the use
    >> of nested switches the best solution?

    >
    > Yes. There is technique called double dispatch which does not use
    > switch or ifs. Somebody asked when is polimorphysm appropriate.
    > Whenever you have case that you have actions based on types,
    > is sure sign to use virtual functions.
    > Simple implementation of your problem is here:
    >
    > #include <iostream>
    > #include <ostream>
    > using namespace std;
    >
    > class Bob1;
    > class Bob2;
    >
    > class BaseBob{
    > public:
    > virtual void accept(BaseBob*)=0;
    > virtual void visit(Bob1*)=0;
    > virtual void visit(Bob2*)=0;


    This is much better:
    virtual void visit( const BaseBob &bob ) = 0;
    then adding a visit() for each bob class.


    > // you can add Bob3
    > virtual ~BaseBob(){}
    > };
    >
    > class Bob1: public BaseBob{
    > public:
    > virtual void accept(BaseBob* b)
    > {
    > b->visit(this);
    > }
    > virtual void visit(Bob1* b)
    > {
    > cout<<"Bob1 says hi to Bob1\n";
    > }


    then do like:
    void visit( const BaseBob &bob )
    {
    std::cout << "Bob" << getId() << " says hi to Bob" << bob.getId()
    << std::endl;
    }

    > virtual void visit(Bob2* b)
    > {
    > cout<<"Bob1 says hi to Bob2\n";
    > }
    > };
    >
    > class Bob2: public BaseBob{
    > public:
    > virtual void accept(BaseBob* b)
    > {
    > b->visit(this);
    > }
    > virtual void visit(Bob1* b)
    > {
    > cout<<"Bob2 says hi to Bob1\n";
    > }
    > virtual void visit(Bob2* b)
    > {
    > cout<<"Bob2 says hi to Bob2\n";
    > }
    > };
    >
    > int main()
    > {
    > Bob1 b1;
    > Bob2 b2;
    > b1.accept(&b1);
    > b1.accept(&b2);
    > b2.accept(&b1);
    > b2.accept(&b2);
    > return 0;
    > }
    anon, May 10, 2007
    #7
  8. desktop

    Guest

    On May 10, 6:15 am, desktop <> wrote:
    > I have 3 types of objects: bob1, bob2 and bob3. Each object is
    > identified by a unique ID which gets returned by the function getId().


    Looks like a classic case of needing virtual functions.

    virtual const string &getName () const = 0 ; // in Base

    const string &bob1::getName () const // In derived.
    {
    return "bob1" ;
    }

    // repeated by class.

    void meet_bob(BaseBob b1, BaseBob b2){
    {
    cout << b1.getName () << " says hi to " << b2.getName () << endl ;
    }
    , May 10, 2007
    #8
  9. "anon" <> wrote in message
    news:f1v3vm$h02$...
    > Branimir Maksimovic wrote:
    >> class Bob1;
    >> class Bob2;
    >>
    >> class BaseBob{
    >> public:
    >> virtual void accept(BaseBob*)=0;
    >> virtual void visit(Bob1*)=0;
    >> virtual void visit(Bob2*)=0;

    >
    > This is much better:
    > virtual void visit( const BaseBob &bob ) = 0;
    > then adding a visit() for each bob class.


    No, by doing that you remove the double dispatch simulation, which is the
    whole point of the visitor pattern. You have two versions of a method, one
    accepting Bob1* and one accepting Bob2*, and you want either one of them to
    be called depending on the type of the argument (which has BaseBob* as it's
    static type). Btw, by doing what you proposed, you're accept method is
    completely obsolete, since that just calls the one visit() method that
    exists, but you still have to implement it in every class.

    As the topicstarter said, this problem is not about simply outputting a
    string, but doing something particular depending on the dynamic types of 2
    arguments (hence double dispatch), rather than just 1 (single dispatch,
    which is supported via virtual methods)

    http://en.wikipedia.org/wiki/Visitor_pattern

    - Sylvester Hesp
    Sylvester Hesp, May 10, 2007
    #9
  10. desktop

    desktop Guest

    Branimir Maksimovic wrote:
    > On May 10, 12:15 pm, desktop <> wrote:
    >> I have 3 types of objects: bob1, bob2 and bob3. Each object is
    >> identified by a unique ID which gets returned by the function getId().
    >>
    >> All bobs are descendants from class BaseBob which is an abstract class
    >> that has the virtual function getId().
    >>
    >> I then have a function that prints a special string when a bob meets
    >> another bob (all combinations of bobs has to meet and since the are
    >> schizophrenic they can also meet themselves):
    >>
    >> void meet_bob(BaseBob b1, BaseBob b2){
    >>
    >> if (b1.getType == 1 && b2.getType == 1)
    >> {
    >> cout << "bob1 says hi to bob1" << endl;
    >> }
    >> else if (b1.getType == 1 && b2.getType == 2)
    >> {
    >> cout << "bob1 says hi to bob2" << endl;
    >> }
    >> else if (b1.getType == 1 && b2.getType == 3)
    >> {
    >> cout << "bob1 says hi to bob3" << endl;
    >> }
    >> else if (b1.getType == 2 && b2.getType == 2)
    >> {
    >> cout << "bob2 says hi to bob2" << endl;
    >> }
    >> else if (b1.getType == 2 && b2.getType == 3)
    >> {
    >> cout << "bob2 says hi to bob3" << endl;
    >> }
    >> ...
    >> ...
    >>
    >> }
    >>
    >> This is not pretty. If I want to use a switch instead I still have to
    >> make a switch for each case in the outer switch and then I have a design
    >> with nested switches instead (which is even uglier).
    >>
    >> Is there some better way to treat this kind of situation or is the use
    >> of nested switches the best solution?

    >
    > Yes. There is technique called double dispatch which does not use
    > switch or ifs. Somebody asked when is polimorphysm appropriate.
    > Whenever you have case that you have actions based on types,
    > is sure sign to use virtual functions.
    > Simple implementation of your problem is here:
    >
    > #include <iostream>
    > #include <ostream>
    > using namespace std;
    >
    > class Bob1;
    > class Bob2;
    >
    > class BaseBob{
    > public:
    > virtual void accept(BaseBob*)=0;
    > virtual void visit(Bob1*)=0;
    > virtual void visit(Bob2*)=0;
    > // you can add Bob3
    > virtual ~BaseBob(){}
    > };
    >
    > class Bob1: public BaseBob{
    > public:
    > virtual void accept(BaseBob* b)
    > {
    > b->visit(this);
    > }
    > virtual void visit(Bob1* b)
    > {
    > cout<<"Bob1 says hi to Bob1\n";
    > }
    > virtual void visit(Bob2* b)
    > {
    > cout<<"Bob1 says hi to Bob2\n";
    > }
    > };
    >
    > class Bob2: public BaseBob{
    > public:
    > virtual void accept(BaseBob* b)
    > {
    > b->visit(this);
    > }
    > virtual void visit(Bob1* b)
    > {
    > cout<<"Bob2 says hi to Bob1\n";
    > }
    > virtual void visit(Bob2* b)
    > {
    > cout<<"Bob2 says hi to Bob2\n";
    > }
    > };
    >
    > int main()
    > {
    > Bob1 b1;
    > Bob2 b2;
    > b1.accept(&b1);
    > b1.accept(&b2);
    > b2.accept(&b1);
    > b2.accept(&b2);
    > return 0;
    > }
    >
    > Greetings, Branimir.
    >


    But does this code scale? If I suddenly have 1000 new bobs I would like
    to add I need to add 1000 "visit" functions to EACH bob object which
    seems as a lot of duplicate work.

    It seems a bit unpractical to implement the visit functionality in the
    Bob's so maybe a nested switch is still the best solution for a large
    number of objects.
    desktop, May 10, 2007
    #10
  11. On 10 Maj, 19:52, desktop <> wrote:
    > Branimir Maksimovic wrote:
    > > On May 10, 12:15 pm, desktop <> wrote:
    > >> I have 3 types of objects: bob1, bob2 and bob3. Each object is
    > >> identified by a unique ID which gets returned by the function getId().

    >
    > >> All bobs are descendants from class BaseBob which is an abstract class
    > >> that has the virtual function getId().

    >
    > >> I then have a function that prints a special string when a bob meets
    > >> another bob (all combinations of bobs has to meet and since the are
    > >> schizophrenic they can also meet themselves):

    >
    > >> void meet_bob(BaseBob b1, BaseBob b2){

    >
    > >> if (b1.getType == 1 && b2.getType == 1)
    > >> {
    > >> cout << "bob1 says hi to bob1" << endl;
    > >> }
    > >> else if (b1.getType == 1 && b2.getType == 2)
    > >> {
    > >> cout << "bob1 says hi to bob2" << endl;
    > >> }
    > >> else if (b1.getType == 1 && b2.getType == 3)
    > >> {
    > >> cout << "bob1 says hi to bob3" << endl;
    > >> }
    > >> else if (b1.getType == 2 && b2.getType == 2)
    > >> {
    > >> cout << "bob2 says hi to bob2" << endl;
    > >> }
    > >> else if (b1.getType == 2 && b2.getType == 3)
    > >> {
    > >> cout << "bob2 says hi to bob3" << endl;
    > >> }
    > >> ...
    > >> ...

    >
    > >> }

    >
    > >> This is not pretty. If I want to use a switch instead I still have to
    > >> make a switch for each case in the outer switch and then I have a design
    > >> with nested switches instead (which is even uglier).

    >
    > >> Is there some better way to treat this kind of situation or is the use
    > >> of nested switches the best solution?

    >
    > > Yes. There is technique called double dispatch which does not use
    > > switch or ifs. Somebody asked when is polimorphysm appropriate.
    > > Whenever you have case that you have actions based on types,
    > > is sure sign to use virtual functions.
    > > Simple implementation of your problem is here:

    >
    > > #include <iostream>
    > > #include <ostream>
    > > using namespace std;

    >
    > > class Bob1;
    > > class Bob2;

    >
    > > class BaseBob{
    > > public:
    > > virtual void accept(BaseBob*)=0;
    > > virtual void visit(Bob1*)=0;
    > > virtual void visit(Bob2*)=0;
    > > // you can add Bob3
    > > virtual ~BaseBob(){}
    > > };

    >
    > > class Bob1: public BaseBob{
    > > public:
    > > virtual void accept(BaseBob* b)
    > > {
    > > b->visit(this);
    > > }
    > > virtual void visit(Bob1* b)
    > > {
    > > cout<<"Bob1 says hi to Bob1\n";
    > > }
    > > virtual void visit(Bob2* b)
    > > {
    > > cout<<"Bob1 says hi to Bob2\n";
    > > }
    > > };

    >
    > > class Bob2: public BaseBob{
    > > public:
    > > virtual void accept(BaseBob* b)
    > > {
    > > b->visit(this);
    > > }
    > > virtual void visit(Bob1* b)
    > > {
    > > cout<<"Bob2 says hi to Bob1\n";
    > > }
    > > virtual void visit(Bob2* b)
    > > {
    > > cout<<"Bob2 says hi to Bob2\n";
    > > }
    > > };

    >
    > > int main()
    > > {
    > > Bob1 b1;
    > > Bob2 b2;
    > > b1.accept(&b1);
    > > b1.accept(&b2);
    > > b2.accept(&b1);
    > > b2.accept(&b2);
    > > return 0;
    > > }

    >
    > > Greetings, Branimir.

    >
    > But does this code scale? If I suddenly have 1000 new bobs I would like
    > to add I need to add 1000 "visit" functions to EACH bob object which
    > seems as a lot of duplicate work.
    >
    > It seems a bit unpractical to implement the visit functionality in the
    > Bob's so maybe a nested switch is still the best solution for a large
    > number of objects.


    If you have that many bobs then either you'd better hope that you
    don't have as many different actions to be taken when they meet each
    other, in which case you can use inheritance and implement the meeting
    in a common base class.

    If not I think you'd need a more generic framework which allows all
    the needed actions to be taken, and the action taken will be
    determined by a lookup in a database of rules which are written in
    some other, higher level language.

    --
    Erik Wikström
    =?iso-8859-1?q?Erik_Wikstr=F6m?=, May 11, 2007
    #11
  12. On May 10, 7:52 pm, desktop <> wrote:
    > Branimir Maksimovic wrote:
    > > On May 10, 12:15 pm, desktop <> wrote:
    > >> I have 3 types of objects: bob1, bob2 and bob3. Each object is
    > >> identified by a unique ID which gets returned by the function getId().

    >
    > >> All bobs are descendants from class BaseBob which is an abstract class
    > >> that has the virtual function getId().

    >
    > >> I then have a function that prints a special string when a bob meets
    > >> another bob (all combinations of bobs has to meet and since the are
    > >> schizophrenic they can also meet themselves):

    >
    > >> void meet_bob(BaseBob b1, BaseBob b2){

    >
    > >> if (b1.getType == 1 && b2.getType == 1)
    > >> {
    > >> cout << "bob1 says hi to bob1" << endl;
    > >> }
    > >> else if (b1.getType == 1 && b2.getType == 2)
    > >> {
    > >> cout << "bob1 says hi to bob2" << endl;
    > >> }
    > >> else if (b1.getType == 1 && b2.getType == 3)
    > >> {
    > >> cout << "bob1 says hi to bob3" << endl;
    > >> }
    > >> else if (b1.getType == 2 && b2.getType == 2)
    > >> {
    > >> cout << "bob2 says hi to bob2" << endl;
    > >> }
    > >> else if (b1.getType == 2 && b2.getType == 3)
    > >> {
    > >> cout << "bob2 says hi to bob3" << endl;
    > >> }
    > >> ...
    > >> ...

    >
    > >> }

    >
    > >> This is not pretty. If I want to use a switch instead I still have to
    > >> make a switch for each case in the outer switch and then I have a design
    > >> with nested switches instead (which is even uglier).

    >
    > >> Is there some better way to treat this kind of situation or is the use
    > >> of nested switches the best solution?

    >
    > > Yes. There is technique called double dispatch which does not use
    > > switch or ifs. Somebody asked when is polimorphysm appropriate.
    > > Whenever you have case that you have actions based on types,
    > > is sure sign to use virtual functions.
    > > Simple implementation of your problem is here:

    >
    > > #include <iostream>
    > > #include <ostream>
    > > using namespace std;

    >
    > > class Bob1;
    > > class Bob2;

    >
    > > class BaseBob{
    > > public:
    > > virtual void accept(BaseBob*)=0;
    > > virtual void visit(Bob1*)=0;
    > > virtual void visit(Bob2*)=0;
    > > // you can add Bob3
    > > virtual ~BaseBob(){}
    > > };

    >
    > > class Bob1: public BaseBob{
    > > public:
    > > virtual void accept(BaseBob* b)
    > > {
    > > b->visit(this);
    > > }
    > > virtual void visit(Bob1* b)
    > > {
    > > cout<<"Bob1 says hi to Bob1\n";
    > > }
    > > virtual void visit(Bob2* b)
    > > {
    > > cout<<"Bob1 says hi to Bob2\n";
    > > }
    > > };

    >
    > > class Bob2: public BaseBob{
    > > public:
    > > virtual void accept(BaseBob* b)
    > > {
    > > b->visit(this);
    > > }
    > > virtual void visit(Bob1* b)
    > > {
    > > cout<<"Bob2 says hi to Bob1\n";
    > > }
    > > virtual void visit(Bob2* b)
    > > {
    > > cout<<"Bob2 says hi to Bob2\n";
    > > }
    > > };

    >
    > > int main()
    > > {
    > > Bob1 b1;
    > > Bob2 b2;
    > > b1.accept(&b1);
    > > b1.accept(&b2);
    > > b2.accept(&b1);
    > > b2.accept(&b2);
    > > return 0;
    > > }

    >
    > > Greetings, Branimir.

    >
    > But does this code scale? If I suddenly have 1000 new bobs I would like
    > to add I need to add 1000 "visit" functions to EACH bob object which
    > seems as a lot of duplicate work.
    >
    > It seems a bit unpractical to implement the visit functionality in the
    > Bob's so maybe a nested switch is still the best solution for a large
    > number of objects.


    If you have 1000 bob objects and your actions are always same,
    so you think you can handle this with one switch case,
    that indicates that you need just one bob class not 1000.
    Seems that you are mixing objects and classes.

    #include <iostream>
    #include <ostream>
    using namespace std;

    class Bob{
    public:
    Bob(unsigned id):id_(id){}
    unsigned getId()const{return id_;}
    void action(const Bob& b)
    {
    cout<<b.getId()<<" says hello to:"<<getId()<<'\n';
    }
    private:
    unsigned id_;
    };

    int main()
    {
    Bob b1(1),b2(2);
    b1.action(b2);
    }

    Greetings, Branimir.
    Branimir Maksimovic, May 11, 2007
    #12
  13. desktop

    James Kanze Guest

    On May 10, 7:52 pm, desktop <> wrote:
    > Branimir Maksimovic wrote:
    > > On May 10, 12:15 pm, desktop <> wrote:
    > >> I have 3 types of objects: bob1, bob2 and bob3. Each object is
    > >> identified by a unique ID which gets returned by the function getId().


    > >> All bobs are descendants from class BaseBob which is an abstract class
    > >> that has the virtual function getId().


    > > Yes. There is technique called double dispatch which does not use
    > > switch or ifs. Somebody asked when is polimorphysm appropriate.
    > > Whenever you have case that you have actions based on types,
    > > is sure sign to use virtual functions.


    Usually.

    > > Simple implementation of your problem is here:


    [...]
    > But does this code scale? If I suddenly have 1000 new bobs I would like
    > to add I need to add 1000 "visit" functions to EACH bob object which
    > seems as a lot of duplicate work.


    If you have 1000 different bob types, and each combination of
    bob types has a different action, then it will be a lot of work,
    regardless of how you do it. That's a million different actions
    that have to be written; it's a nested switch with a total of a
    million different cases (which will probably exceed the resource
    limits of your compiler).

    In general, Branimir has shown you the canonical implementation
    of double dispatch (except that I'd have used references instead
    of pointers). Another alternative is to define a map:

    std::map< std::pair< std::type_info*, std::type_info* >,
    void (*)( bob*, bob* ),
    TypeCmp >

    This allows using free functions, rather than virtual member
    functions, and does avoid having to modify the base class each
    type you add a derived class. But it still requires writing n^2
    functions (and the functions cannot access any member data).
    And the calling sequence is a bit more complicated, since it
    entails a map lookup using the typeid() of both objects.

    A variant I sometimes use, if there is a generic action that I
    can use for most combinations, is to have a simple array of
    pointers to functions, visiting each in order. Each function
    returns true if it works, false otherwise, and I loop over the
    array until one works. Each special function starts something
    like:

    bool
    specialnm( bob* b1, bob* b2 )
    {
    bobn* p1 = dynamic_cast< bobn* >( b1 ) ;
    bonm* p2 = dynamic_cast< bobm* >( b2 ) ;
    if ( p1 != NULL && p2 != NULL ) {
    // special action...
    }
    return p1 != NULL & p2 != NULL ;
    }

    The last function in the array is the generic one, and always
    returns true.

    > It seems a bit unpractical to implement the visit functionality in the
    > Bob's so maybe a nested switch is still the best solution for a large
    > number of objects.


    Double dispatch works on types, not on objects. It's the number
    of types and of distinct actions which counts.

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
    James Kanze, May 11, 2007
    #13
  14. On May 10, 8:15 pm, desktop <> wrote:
    ....
    > Is there some better way to treat this kind of situation or is the use
    > of nested switches the best solution?


    How extenisble does it need to be ? If you need extensibility, you
    should not use a switch or nested if's at all.

    The domain specific part of the question is how much do you need to
    know about each type. A classic problem is shapes and one type of
    thing you want to do to shapes is find out the points of lines of
    intersection. If you have N shapes and you introduce shape N+1 then
    you have N new methods to determine interestcion points, some of these
    can degenerate into combinations of other methodologies but there is
    little you can do but work though each one.

    So, just how complex if your problem in particular ?
    Gianni Mariani, May 11, 2007
    #14
  15. desktop

    desktop Guest

    James Kanze wrote:
    > On May 10, 7:52 pm, desktop <> wrote:
    >> Branimir Maksimovic wrote:
    >>> On May 10, 12:15 pm, desktop <> wrote:
    >>>> I have 3 types of objects: bob1, bob2 and bob3. Each object is
    >>>> identified by a unique ID which gets returned by the function getId().

    >
    >>>> All bobs are descendants from class BaseBob which is an abstract class
    >>>> that has the virtual function getId().

    >
    >>> Yes. There is technique called double dispatch which does not use
    >>> switch or ifs. Somebody asked when is polimorphysm appropriate.
    >>> Whenever you have case that you have actions based on types,
    >>> is sure sign to use virtual functions.

    >
    > Usually.
    >
    >>> Simple implementation of your problem is here:

    >
    > [...]
    >> But does this code scale? If I suddenly have 1000 new bobs I would like
    >> to add I need to add 1000 "visit" functions to EACH bob object which
    >> seems as a lot of duplicate work.

    >
    > If you have 1000 different bob types, and each combination of
    > bob types has a different action, then it will be a lot of work,
    > regardless of how you do it. That's a million different actions
    > that have to be written; it's a nested switch with a total of a
    > million different cases (which will probably exceed the resource
    > limits of your compiler).
    >
    > In general, Branimir has shown you the canonical implementation
    > of double dispatch (except that I'd have used references instead
    > of pointers). Another alternative is to define a map:
    >
    > std::map< std::pair< std::type_info*, std::type_info* >,
    > void (*)( bob*, bob* ),
    > TypeCmp >
    >
    > This allows using free functions, rather than virtual member
    > functions, and does avoid having to modify the base class each
    > type you add a derived class. But it still requires writing n^2
    > functions (and the functions cannot access any member data).
    > And the calling sequence is a bit more complicated, since it
    > entails a map lookup using the typeid() of both objects.
    >
    > A variant I sometimes use, if there is a generic action that I
    > can use for most combinations, is to have a simple array of
    > pointers to functions, visiting each in order. Each function
    > returns true if it works, false otherwise, and I loop over the
    > array until one works. Each special function starts something
    > like:
    >
    > bool
    > specialnm( bob* b1, bob* b2 )
    > {
    > bobn* p1 = dynamic_cast< bobn* >( b1 ) ;
    > bonm* p2 = dynamic_cast< bobm* >( b2 ) ;
    > if ( p1 != NULL && p2 != NULL ) {
    > // special action...
    > }
    > return p1 != NULL & p2 != NULL ;
    > }
    >
    > The last function in the array is the generic one, and always
    > returns true.
    >
    >> It seems a bit unpractical to implement the visit functionality in the
    >> Bob's so maybe a nested switch is still the best solution for a large
    >> number of objects.

    >
    > Double dispatch works on types, not on objects. It's the number
    > of types and of distinct actions which counts.



    But each of my objects are instansiated from class. As I have learned
    creating a new class is the same as creating a new type. So in my case
    if I have 100 different objects I also have 100 classes = 100 types. Its
    these 100 different classes that I make the double dispatch over.
    desktop, May 11, 2007
    #15
  16. On May 11, 1:31 pm, James Kanze <> wrote:
    > On May 10, 7:52 pm, desktop <> wrote:
    >
    >
    > In general, Branimir has shown you the canonical implementation
    > of double dispatch (except that I'd have used references instead
    > of pointers).

    Well, I have adopted practice to use pointer if function has
    non const parameter, and reference if parameter is const,
    because of C programmers that moved to C++ as they
    don't expect for function to change value of parameter
    if it is passed without '&' operator.

    Greetings, Branimir.
    Branimir Maksimovic, May 11, 2007
    #16
  17. On 2007-05-11 16:00, desktop wrote:
    > James Kanze wrote:
    >> On May 10, 7:52 pm, desktop <> wrote:


    [snip]

    >>> It seems a bit unpractical to implement the visit functionality in the
    >>> Bob's so maybe a nested switch is still the best solution for a large
    >>> number of objects.

    >>
    >> Double dispatch works on types, not on objects. It's the number
    >> of types and of distinct actions which counts.

    >
    >
    > But each of my objects are instansiated from class. As I have learned
    > creating a new class is the same as creating a new type. So in my case
    > if I have 100 different objects I also have 100 classes = 100 types. Its
    > these 100 different classes that I make the double dispatch over.


    Yes, but an object is not a class, an object is an instance of a class
    and all instances of a specific class have the same type (namely the class).

    Consider the following:

    // Declare a class Foo
    class Foo {
    int i;
    public:
    Foo(int i_) :i(i_) {}
    };

    // Create a vector storing objects of type Foo
    std::vector<Foo> foos;

    // Create 100 Foo-objects
    for (int i = 0; i < 100; ++i)
    foos.push_back(Foo(i));

    Here I have 1 class (Foo) which is the same as having one type, but I
    have 100 distinct objects, all instances of the Foo class.

    --
    Erik Wikström
    =?ISO-8859-1?Q?Erik_Wikstr=F6m?=, May 11, 2007
    #17
  18. desktop

    James Kanze Guest

    On May 11, 4:00 pm, desktop <> wrote:
    > James Kanze wrote:
    > > On May 10, 7:52 pm, desktop <> wrote:
    > >> Branimir Maksimovic wrote:


    > > Double dispatch works on types, not on objects. It's the number
    > > of types and of distinct actions which counts.


    > But each of my objects are instansiated from class. As I have learned
    > creating a new class is the same as creating a new type. So in my case
    > if I have 100 different objects I also have 100 classes = 100 types. Its
    > these 100 different classes that I make the double dispatch over.


    If you have 100 different types, then you have to write 10000
    functions to handle every combination. There are no two ways
    around that.

    --
    James Kanze (Gabi Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
    James Kanze, May 11, 2007
    #18
  19. desktop

    bnonaj Guest

    desktop wrote:
    > I have 3 types of objects: bob1, bob2 and bob3. Each object is
    > identified by a unique ID which gets returned by the function getId().
    >
    > All bobs are descendants from class BaseBob which is an abstract class
    > that has the virtual function getId().
    >
    > I then have a function that prints a special string when a bob meets
    > another bob (all combinations of bobs has to meet and since the are
    > schizophrenic they can also meet themselves):
    >
    > void meet_bob(BaseBob b1, BaseBob b2){
    >
    > if (b1.getType == 1 && b2.getType == 1)
    > {
    > cout << "bob1 says hi to bob1" << endl;
    > }
    > else if (b1.getType == 1 && b2.getType == 2)
    > {
    > cout << "bob1 says hi to bob2" << endl;
    > }
    > else if (b1.getType == 1 && b2.getType == 3)
    > {
    > cout << "bob1 says hi to bob3" << endl;
    > }
    > else if (b1.getType == 2 && b2.getType == 2)
    > {
    > cout << "bob2 says hi to bob2" << endl;
    > }
    > else if (b1.getType == 2 && b2.getType == 3)
    > {
    > cout << "bob2 says hi to bob3" << endl;
    > }
    > ...
    > ...
    >
    > }
    >
    > This is not pretty. If I want to use a switch instead I still have to
    > make a switch for each case in the outer switch and then I have a design
    > with nested switches instead (which is even uglier).
    >
    > Is there some better way to treat this kind of situation or is the use
    > of nested switches the best solution?


    It would appear to me you need some from of pattern and action object
    lookup table, then a simple loop calling a member function could end
    if the action is executed. This should be expandable and somewhat easier
    to rearrange reliably. In brief you'll need an array/vector of base
    object pointers to test for a particular id pattern, and an object that
    can hold the test pattern and do the test based on how it's configured.
    The code then just becomes a loop something like the following based on
    using vectors for the list of testing objects and list of test objects

    for (iTest = vTest.begin();iTest != vTest.end();++iTest)
    {
    if (iTest->IsDone(vObj))
    {
    break;
    }
    }

    Any suggested improvements, errors or changes are welcome

    JB
    bnonaj, May 15, 2007
    #19
    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. cipcip

    avoid nested function

    cipcip, Apr 9, 2006, in forum: ASP .Net
    Replies:
    1
    Views:
    971
    Chris Fulstow
    Apr 9, 2006
  2. Russ Perry Jr
    Replies:
    2
    Views:
    4,081
    Russ Perry Jr
    Aug 20, 2004
  3. Alexander Malkis
    Replies:
    8
    Views:
    501
    Alexander Malkis
    Apr 14, 2004
  4. Roger23
    Replies:
    2
    Views:
    977
    Roger23
    Oct 12, 2006
  5. Chad E. Dollins
    Replies:
    3
    Views:
    639
    Kai-Uwe Bux
    Nov 8, 2005
Loading...

Share This Page