Avoid nested "if's"?

D

desktop

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?
 
L

Lionel B

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().
 
?

=?iso-8859-1?q?Erik_Wikstr=F6m?=

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



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





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;
 
D

desktop

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


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


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++.
 
K

kingfox

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.
 
B

Branimir Maksimovic

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.
 
A

anon

Branimir said:
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;
}
 
F

faceman28208

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 ;
}
 
S

Sylvester Hesp

anon said:
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
 
D

desktop

Branimir said:
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.
 
?

=?iso-8859-1?q?Erik_Wikstr=F6m?=

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.
 
B

Branimir Maksimovic

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.
 
J

James Kanze


[...]
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.
 
G

Gianni Mariani

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 ?
 
D

desktop

James said:
Branimir said:
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.
 
B

Branimir Maksimovic

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.
 
?

=?ISO-8859-1?Q?Erik_Wikstr=F6m?=

James said:
On May 10, 7:52 pm, desktop <[email protected]> wrote:
[snip]
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.
 
J

James Kanze

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.
 
B

bnonaj

desktop said:
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
 

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,755
Messages
2,569,534
Members
45,008
Latest member
Rahul737

Latest Threads

Top