[comp.lang.c++] Can I make overloaded operator functions virtual?

stuarth@csis.dit.csiro.au (Stuart Hungerford) (03/20/91)

I'd like to be able to make overloaded operator functions virtual.  Say I
had this:

    class Base
    {
    public:
         virtual int operator== (const Base& B);
         //...
    };

then I'd like to override this virtual function in a derived class of Base.
BUT the language requires that overriding virtual functions requires an
exact match so I have to say:

    class Derived : public Base
    {
    public:
        int operator== (const Base& B);
        //...
    };

which means I'll probably end up casting Base classes to Derived ones in the
implementation of Derived::operator==.  I guess the same problem will hold
with any function that takes class references or pointers as arguments.

Am I missing something important here?  How do you handle this kind of 
situation? Any advice much appreciated.

Stuart

+---------------------------------------------------------------------------+
| Internet : stuarth@csis.dit.csiro.au                  |                   |
| Voice    : +61-6-2750941                              |    _--_|\         |
| Fax      : +61-6-2571052                              |   /      \        |
| Postal   : CSIRO Division of Information Technology,  |   \_.--._/        |
|            GPO Box 664, Canberra ACT 2601             |         v         |
|            AUSTRALIA                                  |                   |
+---------------------------------------------------------------------------+

mittle@blinn.watson.ibm.com (Josh Mittleman) (03/22/91)

The problem you describe is precisely the same one addressed earliest this
week under the subject "Seeking neat way to do binary "virtual" functions.
A followup post by cok@islsun.kodak.com suggests some ways around the
problem, but also ntoes that a more fundmanetal solution is really needed.
I've run across the same problem in another context, and I have what might
be a reasonable solution.  I would appreciate comments.

What we need is a new kind of template or prototype for a class, so that
the compiler can be told to treat certain class names as parameters.  To
work with the most recent example:

class Base 
{
public:
  virtual int operator==(const Base&);
};

class Derived : public Base
{
public:
  int operator==(const Derived&);
};

We would really like Derived::operator== to match the virtual
Base::operator==, but we need to be able to treat the argument as a
Derived&.  My solution is somehow to tell the compiler that Base& is to be
treated as a parameter of the parameterized class Base in certain
circumstances.  A possible syntax:

class Base(parameter Base)     // Defines formal parameters
{
public:
  virtual int operator==(parameter const Base&);  // parameterized virtual
						  // function. 
};

This seems to solve the problems suggested, and makes a great deal of sense
to me.  It requires the designer of class Base to forsee which members will
need to be parameterized; I'm not sure if that is a good thing or not.

Comments?

===========================================================================
Josh Mittleman (mittle@ibm.com or joshua@paul.rutgers.edu)
J2-C28 T.J. Watson Research Center, PO Box 704, Yorktown Heights, NY  10598

wmm@world.std.com (William M Miller) (03/22/91)

mittle@blinn.watson.ibm.com (Josh Mittleman) writes:
> class Base
> {
> public:
>   virtual int operator==(const Base&);
> };
>
> class Derived : public Base
> {
> public:
>   int operator==(const Derived&);
> };
>
> We would really like Derived::operator== to match the virtual
> Base::operator==, but we need to be able to treat the argument as a
> Derived&.

No, you really *don't* want that.  Allowing something like that would open a
major hole in the type system.  Assuming Derived::operator==() actually did
override Base::operator==(), code like the following would be possible:

        Base b;
        Derived d;
        Base& br = d;

        if (br == b) ...

Since "br" actually refers to a Derived object, the "==" invokes
Derived::operator==() -- but it passes a Base object as the argument instead
of the Derived object expected by Derived::operator==().  Boom!

This is a frequent request, but it just doesn't make sense when you look at
the ramifications.

-- William M. Miller, Glockenspiel, Ltd.
   wmm@world.std.com

mittle@blinn.watson.ibm.com (Josh Mittleman) (03/23/91)

I wrote:

> class Base
> {
> public:
>   virtual int operator==(const Base&);
> };
> >
> class Derived : public Base
> {
> public:
>   int operator==(const Derived&);
> };
> >
> We would really like Derived::operator== to match the virtual
> Base::operator==, but we need to be able to treat the argument as a
> Derived&.

In article <1991Mar22.044801.26365@world.std.com> William M Miller writes:

> No, you really *don't* want that.  Allowing something like that would open a
> major hole in the type system.  Assuming Derived::operator==() actually did
> override Base::operator==(), code like the following would be possible:
> 
>         Base b;
>         Derived d;
>         Base& br = d;
> 
>         if (br == b) ...
> 
> Since "br" actually refers to a Derived object, the "==" invokes
> Derived::operator==() -- but it passes a Base object as the argument instead
> of the Derived object expected by Derived::operator==().  Boom!

I don't see that this follows.  I am suggesting extension to the way
virtual invocation works.  In your example, you are explicitly asking for
Derived::operator==(Base&).  At compile time, we accept this because it
will always match something, i.e., Base::operator==(Base&).  At run time,
we check to see what the Base& really points to.  If it happened to be a
Base, then we would invoke Base::operator==(Base&).  If it happens to be a
Derived, we invoke Derived::operator==(Derived&).  

The programmer could also provide a member Derived::operator==(Base&), if
he wanted to handle that case in a special way.  

To summarize:

Derived d, e;
Base b, c;
Base &br1 = d, &br2 = e, &br3 = b, &br4 = c;

Derived::operator==(Derived&) invoked by (d == e), (br1 == br2), 
	                                 (br1 == e), (d = br2)

Derived::operator==(Base&)    invoked by (d == b), (br1 == b), (d = br3),
                                         (br1 = br3)

Base::operator==(Base&)       invoked by (b == c), (b == d), (b == br1), 
					 (b = br3), (br3 = c), (br3 = d), 
                                         (br3 = br1), (br3 = br4)

I may be missing something, but I see neither ambiguity nor danger in this
system. 

wmm@world.std.com (William M Miller) (03/23/91)

mittle@blinn.watson.ibm.com (Josh Mittleman) writes:
> To summarize:
>
> Derived d, e;
> Base b, c;
> Base &br1 = d, &br2 = e, &br3 = b, &br4 = c;
>
> Derived::operator==(Derived&) invoked by (d == e), (br1 == br2),
>                                          (br1 == e), (d = br2)
>
> Derived::operator==(Base&)    invoked by (d == b), (br1 == b), (d = br3),
>                                          (br1 = br3)
>
> Base::operator==(Base&)       invoked by (b == c), (b == d), (b == br1),
>                                          (b = br3), (br3 = c), (br3 = d),
>                                          (br3 = br1), (br3 = br4)
>
> I may be missing something, but I see neither ambiguity nor danger in this
> system.

Sorry, this is a much more ambitious modification to the existing virtual
mechanism than I had realized.  When you originally said,

> class Base
> {
> public:
>   virtual int operator==(const Base&);
> };
>
> class Derived : public Base
> {
> public:
>   int operator==(const Derived&);
> };
>
> We would really like Derived::operator== to match the virtual
> Base::operator==, but we need to be able to treat the argument as a
> Derived&.

I had understood that to mean that all you wanted was for
Derived::operator==(const Derived&) to override Base::operator==(const
Base&) and nothing more.  That proposal, which I have seen made by others,
leads directly to the possibility of passing a Base object as the argument
to Derived::operator==(const Derived&), as I pointed out.

What you are asking for, though, as I understand it, is that virtual
dispatch be based not only on the type of the object for which the member
function is invoked but also on the type(s) of the argument(s).  This looks
awfully hard to me unless you go to a full-blown dynamic type resolution
scheme like Objective-C or Smalltalk -- or are you suggesting limiting it
only to 2-level matching (i.e., 1 argument), with the argument required to
be a pointer or reference to the base class first declaring the virtual
function?  If so, it's pretty straightforward to figure out how to do that
with double tables and one extra level of indirection, but it seems like a
rather arbitrary restriction.

Anyhow, this is a new approach to the issue that I hadn't seen before, and
my earlier reaction was based on insufficient information.  I'd be
interested in seeing a more complete writeup on it detailing exactly what
you're proposing (number and type of arguments on which virtual dispatch
could be based, possible implementation strategies, impact on compatibility
with existing code, utility, etc.).  Also, would you be advocating applying
the proposal to non-member overloaded operators?  If you can do virtual
dispatch not just on object type but on operand type as well, it would seem
natural to want "virtual" non-member operators, too.

-- William M. Miller, Glockenspiel, Ltd.
   wmm@world.std.com