"Virtual functions allow polymorphism on a single argument" ?

A

Alf P. Steinbach

* desktop:
This page:

http://www.eptacom.net/pubblicazioni/pub_eng/mdisp.html

start with the line: "Virtual functions allow polymorphism on a single
argument". What does that exactly mean?

I guess it has nothing to do with making multiple arguments in a
declaration like:

virtual void setId(int a, int b) {id = a+b;}

Right. The argument in question is the implicit this-pointer, the
object you're calling the member function on. And what it means is that
what member function implementation to call is selected based on the run
time type of that argument.

Polymorphism on two or more arguments is difficult because the number of
possible function implementations is then the product of the number of
possible classes for each argument.

One useful technique is known as double dispatch; look it up.
 
V

Victor Bazarov

desktop said:
This page:

http://www.eptacom.net/pubblicazioni/pub_eng/mdisp.html

start with the line: "Virtual functions allow polymorphism on a single
argument". What does that exactly mean?

I guess it has nothing to do with making multiple arguments in a
declaration like:

virtual void setId(int a, int b) {id = a+b;}

Sort of. The "single argument" relates to the polymorphism based on the
object itself -- difference in behaviours depending on the dynamic (or
the creation) class of the object. This is (if you read further) put
in opposition to "multi-argument polymorphism" where the behaviour of
a pair of objects should be specific, IOW, along with the object itself
you need to involve the actual explicit argument in defining the
behaviour.

V
 
D

desktop

Alf said:
* desktop:

Right. The argument in question is the implicit this-pointer, the
object you're calling the member function on. And what it means is that
what member function implementation to call is selected based on the run
time type of that argument.

Ok so the argument in question is "obj" in this context:

obj.callMe()

where obj is the object that the member function "callMe()" is called upon.

Since obj can be an instance of B,C or D (if they are all descendants
from a base class A) it is first at runtime it is decided which (B, C or
D) "callme()" function will be run.

Polymorphism on two or more arguments is difficult because the number of
possible function implementations is then the product of the number of
possible classes for each argument.


But how can there be more than one object that a function is called
upon? As I see it there can only be one (like obj) but it might differ
at runtime which type it is.

One useful technique is known as double dispatch; look it up.

I am currently reading this pattern but need to understand what they
mean with polymorphism with one or two arguments.
 
R

Rolf Magnus

desktop said:
Ok so the argument in question is "obj" in this context:

obj.callMe()

where obj is the object that the member function "callMe()" is called
upon.

obj must be a reference, otherwise there is no polymorphism.
Since obj can be an instance of B,C or D (if they are all descendants
from a base class A) it is first at runtime it is decided which (B, C or
D) "callme()" function will be run.




But how can there be more than one object that a function is called
upon? As I see it there can only be one (like obj) but it might differ
at runtime which type it is.


Right, and that's exactly the reason why this is "polymorphism on a single
argument".
I am currently reading this pattern but need to understand what they
mean with polymorphism with one or two arguments.

It simply means that the function that is seleccted at runtime depends on
the dynamic type of one or two objects.
 
J

James Kanze

Ok so the argument in question is "obj" in this context:

where obj is the object that the member function "callMe()" is called upon.

Yes. In C++ syntax. Conceptually, this can be mapped to
callMe( obj ), with polymorphism always occuring on the first
object (in C++).

Now consider something like "obj.callMe( arg )". Conceptually,
this would be "callMe( obj, arg )". In C++, polymorphism only
works on the first argument here. In other languages, the
actual function called can depend on the dynamic type of both
arguments, e.g. in CLOS: "(callMe obj arg)", the actual function
called can depend on the type of obj, the type of arg or both
(or neither).

As Alf pointed out, this can get a bit hairy: if both arguments
can have 10 different types, this means 100 different functions.
Which you have to write. And to add a new type, you practically
have to know all of the existing types, in order to add all of
the additional functions.

In practice, in C++, this can be implemented by calling a
virtual function on the first object, and having it call a
virtual function on the second. But with the constraint that
the base class has to know of the existance of all of the
derived types.
 
D

desktop

James said:
Yes. In C++ syntax. Conceptually, this can be mapped to
callMe( obj ), with polymorphism always occuring on the first
object (in C++).

Now consider something like "obj.callMe( arg )". Conceptually,
this would be "callMe( obj, arg )". In C++, polymorphism only
works on the first argument here. In other languages, the
actual function called can depend on the dynamic type of both
arguments, e.g. in CLOS: "(callMe obj arg)", the actual function
called can depend on the type of obj, the type of arg or both
(or neither).

As Alf pointed out, this can get a bit hairy: if both arguments
can have 10 different types, this means 100 different functions.
Which you have to write. And to add a new type, you practically
have to know all of the existing types, in order to add all of
the additional functions.

In practice, in C++, this can be implemented by calling a
virtual function on the first object, and having it call a
virtual function on the second. But with the constraint that
the base class has to know of the existance of all of the
derived types.



Ok, if we have obj.callMe(arg) the "first" makes sure to find the right
"owner" of obj and uses the virtual functionality to accomplish this.

The "owner object" has as many callMe(arg) functions (specified as
virtual in an abstract base-class) as there are different types of "arg"
objects. In the owner object it could look like this:

callMe(Ball) {}
callMe(Plane) {}
callMe(Bus) {}
callMe(Toy) {}


The "second" call as I understand is just a matter of finding the right
function matching the called argument "arg" through the overloaded
function callMe.

You say that multiple-arg polymorphism deals with two virtual calls, but
are the second not just a regular call to an overloaded function? Or
does it only hold for this example?
 
J

James Kanze

Ok, if we have obj.callMe(arg) the "first" makes sure to find the right
"owner" of obj and uses the virtual functionality to accomplish this.
The "owner object" has as many callMe(arg) functions (specified as
virtual in an abstract base-class) as there are different types of "arg"
objects. In the owner object it could look like this:
callMe(Ball) {}
callMe(Plane) {}
callMe(Bus) {}
callMe(Toy) {}
The "second" call as I understand is just a matter of finding the right
function matching the called argument "arg" through the overloaded
function callMe.
You say that multiple-arg polymorphism deals with two virtual calls, but
are the second not just a regular call to an overloaded function?

No. The first virtual function call resolves the type of the
first object. It ends up in a function of this type, with a
fully typed this. It then calls a virtual function on the
second object, which takes the resolved type as argument. All
of the functions must be present and virtual in the base class,
so we get something like:

class Vehicule
{
public:
virtual void collideWith( Vehicule& other ) = 0 ;

virtual void collideWith( Car& other ) = 0 ;
virtual void collideWith( Bus& other ) = 0 ;
virtual void collideWith( Truck& other ) = 0 ;
// ...
} ;

class Car : public Vehicule
{
public:
virtual void collideWith( Vehicule& other )
{
other.collideWith( *this ) ;
}

virtual void collideWith( Car& other )
{
// Car vs. Car...
}
virtual void collideWith( Bus& other )
{
// Bus vs. Car...
}
virtual void collideWith( Truck& other )
{
// Truck vs. Car...
}
} ;

Vehicule* pCar = new Car ;
Vehicule* pBus = new Bus ;

pCar->collideWith( *pBus ) ;

So what happens here. First, the compiler decides *statically*
which function is to be called (overload resolution), in
Vehicule. Since the argument has the static type Vehicule
(because it is a Vehicule* which is dereferences), the compiler
chooses Vehicule::collideWith( Vehicule& ). Overload resolution
is *always* based on the static type. Since this function is
declared virtual, virtual dispatch occurs, and as a result, we
end up in Car::collideWith( Vehicule& ). But all this does is
call collideWith on the other object. So we start over:
overload resolution in the base class. Except that this time,
the argument is *this, this is a Car* (and not a Vehicule*), so
oeverload resolution chooses Vehicule::collideWith( Car& ) (and
not Vehicule::collideWith( Vehicule& ), as it did the first
time. Again, this function is declared virtual, so virtual
dispatch is applied to "other"; since the actual type is a Bus,
we end up in Bus::collideWith( Car& ) (which I haven't shown).

Basically, each dynamic dispatch works on a single object.
Using this pattern, the first dispatch serves to get us into a
context where the static type is the same as that as the first
object. We then inverse the call, so that the second static
dispatch will call a function based on the type of what was
originally the second argument, with the already resolved type
being used to choose the correct function, by means of overload
resolution.
 

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,768
Messages
2,569,575
Members
45,053
Latest member
billing-software

Latest Threads

Top