[comp.std.c++] Dynamic Binding by Argument Type

johnt@meaddata.com (John Townsend) (02/06/91)

Suppose that you have the following classes:

    class Base {
    public:
	virtual void f();
    }

    class Derived: public Base {
    public:
	virtual void f();
    }

and, elsewhere, you have the following overloaded functions:

    void g (Base &arg);
    void g (Derived &arg);

Clearly, dynamic binding can be used as follows:

    Base *b1, b2;
    Derived d1;

    b1 = &b2;
    b1->f();       // Calls f() in Base class
    (*b1).f();     // Calls f() in Base class
    b1 = &d1;
    b1->f();       // Calls f() in Derived class
    (*b1).f();     // Calls f() in Derived class

Now, consider this:

    b1 = &b2;
    g(*b1);        // Calls g(Base &arg)
    b1 = &d1;
    g(*b1);        // STILL calls g(Base &arg), even though *b1 is a Derived
		   // object!

Why is it that *b1 is treated as a Derived object when it is the invoking
object, but as a Base object when it is the argument?  Is this intentional?
It seems somewhat inconsistent to me.  Is there some way to dynamically bind
to the overloaded g() based on its argument type?

--
     John Townsend                 Internet:   johnt@meaddata.com
 c/o Mead Data Central             UUCP:       ...!uunet!meaddata!skibum!johnt
     P.O. Box 933                  Telephone:  (513) 865-7250 
  Dayton, Ohio, 45401

fuchs@it.uka.de (Harald Fuchs) (02/06/91)

johnt@meaddata.com (John Townsend) writes:

>Now, consider this:

>    b1 = &b2;
>    g(*b1);        // Calls g(Base &arg)
>    b1 = &d1;
>    g(*b1);        // STILL calls g(Base &arg), even though *b1 is a Derived
>		   // object!

>Why is it that *b1 is treated as a Derived object when it is the invoking
>object, but as a Base object when it is the argument?  Is this intentional?

Me thinks so. There is no way you could tell g's argument to use
static or dynamic binding. What you might want is something like
    void g (virtual Base&);
but that's not C++.

>It seems somewhat inconsistent to me.  Is there some way to dynamically bind
>to the overloaded g() based on its argument type?

Sure:
    class Base {
    public:
        virtual void f();
        virtual void g ();
    }

    class Derived: public Base {
    public:
        void f();
        void g ();
    }

    inline void g (Base& arg) { arg.g (); }
--

Harald Fuchs <fuchs@telematik.informatik.uni-karlsruhe.de>
<fuchs@telematik.informatik.uni-karlsruhe.dbp.de>   *gulp*

johnt@meaddata.com (John Townsend) (02/06/91)

In article <fuchs.665772943@tmipi4>, fuchs@it.uka.de (Harald Fuchs) writes:
|> johnt@meaddata.com (John Townsend) writes:
|> 
|> >Now, consider this:
|> 
|> >    b1 = &b2;
|> >    g(*b1);        // Calls g(Base &arg)
|> >    b1 = &d1;
|> >    g(*b1);        // STILL calls g(Base &arg), even though *b1 is a
|> >                   // Derived object!
|> 
|> >Why is it that *b1 is treated as a Derived object when it is the invoking
|> >object, but as a Base object when it is the argument?  Is this intentional?
|> 
|> Me thinks so. There is no way you could tell g's argument to use
|> static or dynamic binding. What you might want is something like
|>     void g (virtual Base&);
|> but that's not C++.

This has occurred to me.  The obvious next question is, SHOULD it be C++?  Is
there a good reason why this should not be allowed?

|> >It seems somewhat inconsistent to me.  Is there some way to dynamically
|> >bind to the overloaded g() based on its argument type?
|> 
|> Sure:
|>     class Base {
|>     public:
|>         virtual void f();
|>         virtual void g ();
|>     }
|> 
|>     class Derived: public Base {
|>     public:
|>         void f();
|>         void g ();
|>     }
|> 
|>     inline void g (Base& arg) { arg.g (); }

This will work, of course, but in my intended application the g() functions
are already member functions of another class, and must remain so (they
wouldn't make sense as members of Base and Derived, and don't require access
to the representations of these classes).  

Again, the problem as I see it is that if b1 points to a Derived object, then
*b1 is treated as a Derived object if it is an invoking object, but as a Base
object in other contexts (such as an argument).  It is impossible to externally
use the additional members of a Derived object once it has been assigned to a
Base pointer.  If *b1 is a Derived object, I want to be able to pass it to a
function that expects a Derived object and uses those members.

Thanks, Harald, for your thoughts on the subject!

--
     John Townsend                 Internet:   johnt@meaddata.com
 c/o Mead Data Central             UUCP:       ...!uunet!meaddata!skibum!johnt
     P.O. Box 933                  Telephone:  (513) 865-7250 
  Dayton, Ohio, 45401

jimad@microsoft.UUCP (Jim ADCOCK) (02/14/91)

In article <2715@meaddata.meaddata.com> johnt@meaddata.com (John Townsend) writes:
....
>Why is it that *b1 is treated as a Derived object when it is the invoking
>object, but as a Base object when it is the argument?  Is this intentional?

I believe this design choice was intentional.  If one generalizes g() to 
n parameters you get n-dimensional dispatch: g(x, y, z) where if x, y, and z
can each be one of 20 classes would be selecting a particular g() out
of 8000 possible choices.  This could be done --but its hard to imagine compiler
schemes to automatically handle these requirements, and simultaneously
be relatively efficient of excutable time and space.  Simple, fast, 
techniques are readily available to do one dimensional dispatch, and that's 
where C++ draws the line.

In your case, where g(ob) is itself a member, you'd be in general asking
for 2D dispatch, g(ob1, ob2) would be 3D dispatch ....

Obviously, people would not typically overload all 8000 functions mentioned
above.  Thus, we'd have to figure out a new set of rules as to which of
the previously defined functions are suppose to be called.  The rules for
picking the "closest match" function in N-dimensions are not clear to 
me.  Care to define and clearly explain such a set of rules?

>It seems somewhat inconsistent to me.  Is there some way to dynamically bind
>to the overloaded g() based on its argument type?

Your request can be broken down into two parts.  The first could be considered:
"Is there some way to use C-function syntax instead of member-function 
 syntax?"

The answer to this part is trivially answered yes:

inline C_g(SOMECLASS& ob) { ob.member_g(); }

Now that we've gotten the syntax issue out of the way, the question 
becomes:  "Is there some way to add a member function "g" to an 
existing set of classes?"

The answer to this part of the question is yes too, if ugly:



extern "C" void printf(const char*, ...);

class B { public: virtual void f(); };
void B::f() { printf("B::f()\n"); } 

class D : public B { public: virtual void f(); };
void D::f() { printf("D::f()\n"); } 

/***** pretend the above classes are immutable *****/	

/***** then your g(ob) functionality can be met as follows *****/

class G { public: virtual void g(); };
/*** below should be pure virtual, depending on your compiler ****/
void G::g() { printf("G::g() shouldn't be called at all! "); } 

class BG : public G, public B { public: virtual void g(); };
void BG::g() { printf("B::g() can use members like "); f(); };

class DG : public G, public D
{ 
public:
	virtual void g();

	// normally, the below would be unfair, but in this case, a DG *is*
	// both a B and a G, and therefore a BG.  
	operator BG&();
};
DG::operator BG&() { return *((BG*)((G*)(this))); }
void DG::g() { printf("D::g() can use members like "); f(); } 

void g(BG& b) { b.g(); }
void g(DG& d) { d.g(); }

#define B BG
#define D DG

/**** now let's try the new "enhanced" versions of B and D ****/

main()
{
	B b;
	D d;
	g(b);
	g(d);

	B& br = d;
	D& dr = d;
	g(br);
	g(dr);
}

-----------

Obviously, this approach works just as well if ::g(DG&) and ::g(BG&) are
members of some class, rather than globals.

[I am not recommending this approach, mearly pointing out that it
can be done.]