Floogle wrote:
how do i create a virtual == operator. I've tried the following but
it's incorrect...
The short answer is that you use an more powerful object-oriented
programming system in which a virtual function is dispatched by
considering the dynamic types of *all* of the specializable arguments.
There is a crutch design pattern that you can use in a less powerful
object system, like that of C++, to emulate multiple dispatch. You end
up making two virtual function calls.
The first virtual call dynamically dispatches on the left object, and
goes to a stub function, whose only purpose is to dispatch one more
time on the right object.
This is done in the ``Visitor Pattern'' for instance. The problem with
that pattern is that it uses generic terminology like ``visit'' and
``accept'' which obscures the semantics of what the user is actually
implementing. You can rip out the double dispatch trick, without
taking in the whole pattern in.
class Interface
{
...
public:
virtual bool operator==(const Interface& rhs)const=0;
};
class MyClass : public Interface
{
...
public:
bool operator==(const MyClass& rhs)const;
};
Of course, the function you have here in MyClass is not an overload of
the base class virtual function, because the type signature does not
match. You must in fact implement:
bool operator==(const Interface &rhs) const;
So now, the problem is that this dispatches only on the type of the
object on which the virtual is called. You know that your ``this''
pointer is a MyClass, but you need to handle all combinations of
MyClass and everything else. The trick is to invoke another virtual
function, this time on the rhs object:
bool MyClass::operator==(const Interface &rhs) const
{
return rhs.operator == (*this);
}
In this second virtual call, the arguments are reversed: the parameter
is now const MyClass & and static overload resolution is being used to
find the method. That's because we know the exact type of the left hand
side object!
All you need now is additional virtual functions inside Interface which
are specialized to various types of objects.
// Inside Interface base:
virtual bool operator==(const MyClass& rhs) const = 0;
virtual bool operator==(const YourClass &rhs) const = 0;
// .. etc ... for every darn class! Every time you add a class
// to your framework, you have to add an entry here, and
// implement the combination throughout the entire framework!!!
So for instance the combination MyClass X MyClass -> bool is handled by
writing an additional function in MyClass:
bool MyClass::operator == (const MyClass &lhs) const
{
}
and the YourClass X MyClass -> bool combination is handled like this:
// You HAVE to implement this because it's a pure virtual
// inside the Interface base!!!
bool MyClass::operator == (const YourClass &lhs) const
{
// handle the combination here.
}
and so on. I'm calling it lhs because the order is reversed; we are at
the second dispatch level, where we invoked the virtual function on the
right hand side object in the original ==() call! The original left
hand object is now the argument.
One thing you might want to do is use a different name for the two
steps, like in the visitor pattern, which has visit() for the first
call and accept() for the other. Confusion can occur because some of
the == functions can be called non-virtually, when you aren't going
through base classes. In this case, it should all be cool because the
comparison is commutative (right?)
That is to say, if you have two MyClass objects and you compare them
with ==, then it will just go to the operator == (const MyClass &)
right away without the double dispatch, and the right hand side will be
assumed to be the left.
To avoid that, do it like this:
class Interface {
public:
// entry point into comparison
virtual bool operator == (const Interface &rhs) const = 0;
// second dispatch completion routines, distinct from operator
virtual bool eq_impl(const MyClass &from_lhs) const = 0;
virtual bool eq_impl(const YourClass &from_lhs) const = 0;
// repeat for every darn class, implement all combos
};
The operator == implementation is the same everywhere:
return rhs.eq_impl(*this);
everyone must implement this. Everyone must also implement every
eq_impl for every left hand class.
This could be extended to triple dispatch:
virtual void func(Interface &B, Interface &C) = 0;
Let's refer to the first object as the hidden parameter ``A'', so the
manifest parameters are B and C.
At the first level, the type of the object is established. So now, it
can invoke a second level virtual, invoked on Interface B. The ``A''
object now appears as a concrete parameter with an exact class. C
remains abstract:
virtual void func2(Concrete &A, Interface &C) = 0;
here, the exact type of A and B is known, so a final virtual call can
take place on object C, which statically chooses a virtual based on
these two types:
virtual void func3(Concrete &A, Concrete &B) = 0;
Note that if you have M implementations of the interface, then you need
M implementations of func(), M * M implementations of func2(), and M *
M * M implementations of func3().
Probably a good idea to make some of these impure, so you can inherit
default behaviors and not have to deal with all the combos.