Topic: Objects already contain type information => binary virtual functions


Author: ngo@tammy.harvard.edu (Tom Ngo)
Date: 28 Mar 91 14:36:48 GMT
Raw View
Here's a wild idea whose purpose is to permit typesafe downcasts under
certain circumstances.  Do others think it's worth pursuing?  Can it
be generalized?

The problem:

    class B {
        virtual void foo(B*);
    };
    class D : public B {
        virtual void foo(D*);
    };

    void bar(B* b1, B* b2) {
        b1.foo(b2);
    }

    D d1;
    D d2;
    bar(&d1,&d2);            // I want D::foo to be invoked

    Currently, D::foo does not override B::foo.  A way to cause this
    to happen is for D::foo to be declared D::foo(B*) and downcast B*
    to D* within the body of D::foo.   This downcast is considered
    unsafe; see ARM p. 210-211.

    I call foo() a "binary" virtual function, meaning that the
    intention is for it to operate on two objects of identical type,
    even if the call is made with references or pointers to a base
    class of the type.

    I feel there is a strong need for such functions, and that
    currently there is no way to implement them that is both typesafe
    and elegant.  For example, if you have a container class designed
    to take B*'s and you have given it all D*'s, the container class
    ought to be able to do things like compare objects in a D-specific
    manner, etc.  Right now if you want to implement this sort of
    thing in a typesafe manner you have to make two separate virtual
    function calls, one for each operand of the comparison, or some
    such thing.

The proposed solution:

    It has been argued that to implement typesafe downcasts would
    require that type information be added to each object.  I suggest
    that objects already contain type information in their virtual
    function tables.  So here's the idea:

    1.  Relax the requirement that two functions' arguments match
        EXACTLY for one to override the other.  Instead, permit
        D::foo(D*) to match B::foo(B*).

        (In general, any argument of type D* or D* in the argument
        list of D::foo would match an argument of type B* or B* in
        B::foo.)

    2.  In that case, in D::foo(D* that) the compiler should
        automatically generate code that ensures:

            this->foo == that->foo

        (You know what I mean.)  If the equality fails, then perhaps
        an addressing exception should be generated.

    This solution generalizes trivially to cases in which D::foo has
    several D* arguments.

Comments?

--Tom
--
  Tom Ngo
  ngo@harvard.harvard.edu
  617/495-1768 lab number, leave message




Author: chip@tct.com (Chip Salzenberg)
Date: 31 Mar 91 19:47:51 GMT
Raw View
According to ngo@tammy.harvard.edu (Tom Ngo):
>The problem:
>
>    class B {
>        virtual void foo(B*);
>    };
>    class D : public B {
>        virtual void foo(D*);
>    };
>
>    void bar(B* b1, B* b2) {
>        b1.foo(b2);
>    }
>
>    D d1;
>    D d2;
>    bar(&d1,&d2);            // I want D::foo to be invoked

To suppose that D::foo() should override B::foo() is to misunderstand
the meaning of virtual functions, namely:

    A virtual function is _one_ function with potentially many
    implementations.

The code quoted above shows that D::foo() is prepared to work on a D,
while B::foo() is prepared only for a B.  Thus they are not the same
function.  They may be equivalent in one way or another; they may even
have the same body.  But they are not the _same_ function.  So the use
of "virtual" for these two functions is a design error.
--
Chip Salzenberg at Teltronics/TCT     <chip@tct.com>, <uunet!pdn!tct!chip>
   "All this is conjecture of course, since I *only* post in the nude.
    Nothing comes between me and my t.b.  Nothing."   -- Bill Coderre