C++Talk.NET Forum Index C++Talk.NET
C++ language newsgroups
 
Archives   FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Why this does not work ?? (inheritance and virtual operator=

 
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ Language (Moderated)
View previous topic :: View next topic  
Author Message
psane
Guest





PostPosted: Tue Mar 02, 2004 11:32 am    Post subject: Why this does not work ?? (inheritance and virtual operator= Reply with quote



I have the following code

#include <iostream>
#include <string>
using namespace std;

class superclass
{
public:
superclass () {}

virtual bool operator== (const superclass& rhs) const;

virtual void doit (const superclass* rhs) const;

virtual string Id () const { return "superclass";}
};

void
superclass::doit (const superclass* rhs) const
{
cout << "this->Id = " << Id () << endl;
cout << "rhs->Id = " << rhs->Id () << endl;

*this == *rhs;
}

bool
superclass::operator== (const superclass& rhs) const
{
cout << "in superclass operator==" << endl;
return true;
}

///////////////////////////////////////////////////////////////////////////

class subclass : public superclass
{
public:
subclass () : superclass () {}

virtual bool operator== (const subclass& rhs) const;
#if 0
virtual bool operator== (const superclass& rhs) const;
#endif
virtual string Id () const { return "subclass";}
};

#if 0
bool
subclass::operator== (const superclass& rhs) const
{
cout << "in subclass 2 operator==" << endl;
return true;
}
#endif

bool
subclass::operator== (const subclass& rhs) const
{
cout << "in subclass operator==" << endl;
return true;
}
int
main (int argc, char* argv[])
{
subclass x;
subclass y;

x.doit (&y);

return 0;
}


The output I get is

this->Id = subclass
rhs->Id = subclass
in superclass operator==

However, if I change #if 0s to #if 1, I get correct (expected) output.

this->Id = subclass
rhs->Id = subclass
in subclass 2 operator==

It looks like the dynamic datatype of RHS is not being determined
correctly for operator==. Why is this so ?? How do I fix this ?? (I
really need to call doit () in the superclass ...

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Back to top
Stephen C. Dewhurst
Guest





PostPosted: Tue Mar 02, 2004 8:56 pm    Post subject: Re: Why this does not work ?? (inheritance and virtual opera Reply with quote



On 2 Mar 2004 06:32:00 -0500, [email]pbsane (AT) hotmail (DOT) com[/email] (psane) wrote:


Quote:
It looks like the dynamic datatype of RHS is not being determined
correctly for operator==. Why is this so ?? How do I fix this ?? (I
really need to call doit () in the superclass ...

The static type of *this in superclass::doit is superclass, so it
makes a virtual function call to the only operator== that is in the
scope of superclass. That function takes an argument of type const
superclass &. Therefore, if the derived class operator == is to
override that base class virtual function, it must declare an operator
== that takes an argument of the same type. (The derived class
function has some flexibility in what it may return, known as
covarient return types, but no flexibility with the argument type.)
Your second operator == in subclass did override the base class
operator ==.

This problem is discussed in Gotcha #76 of C++ Gotchas, in the context
of copy assignment. My recommendation there is, essentially, not to
do it, and use a different mechanism. Things are not as problematic
with operator ==, however. You might consider defining a non-virtual
(and potentially non-member) operator == for superclass that calls
protected or private virtual functions that actually do the
comparison. That way you can avoid the strangeness of having a public
derived class operator == that takes a base class rhs. There will
still be complexities to sort out, however, such as what you want to
happen when a base class object is compared to a derived class object.
That's not necessarily wrong, just complex, and I prefer to avoid
complexity unless it's essential.

Steve

Steve Dewhurst
www.semantics.org

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Back to top
Ben Hutchings
Guest





PostPosted: Tue Mar 02, 2004 9:01 pm    Post subject: Re: Why this does not work ?? (inheritance and virtual opera Reply with quote



psane wrote:
<snip>
Quote:
It looks like the dynamic datatype of RHS is not being determined
correctly for operator==. Why is this so ??

It is being determined correctly - overload resolution is done
statically, i.e. at compile time.

Quote:
How do I fix this ?? (I really need to call doit () in the
superclass ...

You need a multimethod. I don't have experience with these; try
a web search for "multimethod" and "C++".

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Back to top
Stephen C. Dewhurst
Guest





PostPosted: Wed Mar 03, 2004 11:43 am    Post subject: Re: Why this does not work ?? (inheritance and virtual opera Reply with quote

On 2 Mar 2004 16:01:51 -0500, Ben Hutchings
<do-not-spam-benh (AT) bwsint (DOT) com> wrote:

Quote:
psane wrote:
snip
It looks like the dynamic datatype of RHS is not being determined
correctly for operator==. Why is this so ??

It is being determined correctly - overload resolution is done
statically, i.e. at compile time.

How do I fix this ?? (I really need to call doit () in the
superclass ...

You need a multimethod. I don't have experience with these; try
a web search for "multimethod" and "C++".

It's true that a multimethod will work here (see Alexandrescu's Modern
C++ Design for more information), as would (more or less equivalently)
application of some variant of the Visitor Pattern (Gamma, et al.
Design Patterns). However, I really think that, in this case, that's
vast overkill, and the negative effects of applying such a complex
solution in what should be a simple design far outweigh the positive
effects.

It may be that the best solution is to avoid use of operator == in the
base class, and simply have a compareId function instead, if that's
what's really going on. If that's not what's going on, then decide
what is going on, and name the function appropriately.

Steve

Steve Dewhurst
www.semantics.org

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Back to top
kanze@gabi-soft.fr
Guest





PostPosted: Wed Mar 03, 2004 8:30 pm    Post subject: Re: Why this does not work ?? (inheritance and virtual opera Reply with quote

"Stephen C. Dewhurst" <scd (AT) semantics (DOT) org> wrote


Quote:
On 2 Mar 2004 06:32:00 -0500, [email]pbsane (AT) hotmail (DOT) com[/email] (psane) wrote:

It looks like the dynamic datatype of RHS is not being determined
correctly for operator==. Why is this so ?? How do I fix this ?? (I
really need to call doit () in the superclass ...

The static type of *this in superclass::doit is superclass, so it
makes a virtual function call to the only operator== that is in the
scope of superclass. That function takes an argument of type const
superclass &. Therefore, if the derived class operator == is to
override that base class virtual function, it must declare an operator
== that takes an argument of the same type. (The derived class
function has some flexibility in what it may return, known as
covarient return types, but no flexibility with the argument type.)
Your second operator == in subclass did override the base class
operator ==.

This problem is discussed in Gotcha #76 of C++ Gotchas, in the context
of copy assignment. My recommendation there is, essentially, not to
do it, and use a different mechanism. Things are not as problematic
with operator ==, however.

That depends. The problems are certainly less than those of assignment,
but the virtual == only makes sense if I can use it when the left and
right sides have different types. This leads us to the double dispatch
pattern, and potentially, n^2 functions (where n is the total number of
different types involved) and every class having to know every other
class in the hierarchy.

One case where it might make sense is if we can define from the start
that == returns false if the types are different. In that case, of
course, it is sufficient to dispatch on the left hand side, and simply
check the right hand side, e.g.:

bool
subclass::operator==( superclass const& rhs )
{
subclass const* other = dynamic_cast< subclass const* >( &rhs ) ;
return other != NULL && operator==( *other ) ;
}

Except that I'd probably go for a named member function (isEqual), and
use a free function for operator== and operator!=.

FWIW: if you really need the double dispatch, then something like the
following can be used:

class superclass
{
public:
virtual bool isEqual( superclass const& other ) const ;
virtual bool isEqual( subclass1 const& other ) const ;
virtual bool isEqual( subclass2 const& other ) const ;
// For all possible subclasses...
} ;

and systematically, in each subclass:

bool
subclass1::isEqual( superclass const& other ) const
{
return other.isEqual( *this ) ;
}

plus, of course, the relevant implementation for the comparison with the
other subclasses. (As written, this supposes that the superclass is
abstract. All of the isEqual functions in the superclass should
probably be pure virtual.)

--
James Kanze GABI Software mailto:kanze (AT) gabi-soft (DOT) fr
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Back to top
Niklas Matthies
Guest





PostPosted: Fri Mar 05, 2004 10:35 am    Post subject: Re: Why this does not work ?? (inheritance and virtual opera Reply with quote

On 2004-03-03 20:30, [email]kanze (AT) gabi-soft (DOT) fr[/email] wrote:
Quote:
"Stephen C. Dewhurst" <scd (AT) semantics (DOT) org> wrote:
:
Things are not as problematic with operator ==, however.

That depends. The problems are certainly less than those of assignment,
but the virtual == only makes sense if I can use it when the left and
right sides have different types. This leads us to the double dispatch
pattern, and potentially, n^2 functions (where n is the total number of
different types involved) and every class having to know every other
class in the hierarchy.

Not really, IMHO. In Java, equality comparison is commonly implemented
using the following pattern:

class C ...
{
public static boolean equals(C a, C b)
{
return ...;
}

public boolean equals(Object obj)
{
if (obj instanceof C)
{
return equals(this, (C) obj);
}
else
{
return super.equals(obj);
}
}
}

It's not obvious at first, but this causes the equals() of the most
derived common base class of the two objects being compared to be
invoked, which ensures both symmetry and associativity of '=='.
I've yet have to come across a situation where this is not the
required (due to LSP considerations) and most natural behaviour.

In C++ it is possible that the (moral equivalent of) super.equals()
call becomes ambiguous in the case of multiple inheritance. Depending
on what "equality" means for the base classes, this is either a design
error, or else the correct thing to do is to logically "and" the
(possibly) different equalities from the common subset of the
appropriate base classes of the two objects that are compared.

Something like:

struct eq_comparable
{
virtual bool equals(eq_comparable const & obj) const
{
return this == obj; // default implementation
}
};

struct Derived
: Base1, Base2, ..., BaseN // all eq_comparable
{
template <class T>
bool eq_helper(bool & match, eq_comparable const & obj) const
{
T const * t = dynamic_cast<T const *>(&obj);
if (t == 0)
{
return true;
}
else
{
match = true;
return T::equals(*this, *t);
}
}

static bool equals(Derived const & a, Derived const & b) const
{
return ...;
}

virtual bool equals(eq_comparable const & obj) const
{
bool match = false;

if (eq_helper<Derived>(match, obj) && match) return true;

if (!eq_helper<Base1>(match, obj)) return false;
if (!eq_helper<Base2>(match, obj)) return false;
:
if (!eq_helper<BaseN>(match, obj)) return false;

return match;
}
};

-- Niklas Matthies

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Back to top
Stephen C. Dewhurst
Guest





PostPosted: Fri Mar 05, 2004 2:42 pm    Post subject: Re: Why this does not work ?? (inheritance and virtual oper Reply with quote

On 5 Mar 2004 05:35:39 -0500, Niklas Matthies <usenet-nospam (AT) nmhq (DOT) net>
wrote:

Quote:
On 2004-03-03 20:30, [email]kanze (AT) gabi-soft (DOT) fr[/email] wrote:
"Stephen C. Dewhurst" <scd (AT) semantics (DOT) org> wrote:
:
Things are not as problematic with operator ==, however.

That depends. The problems are certainly less than those of
assignment,
but the virtual == only makes sense if I can use it when the left and
right sides have different types. This leads us to the double
dispatch
pattern, and potentially, n^2 functions (where n is the total number
of
different types involved) and every class having to know every other
class in the hierarchy.

Not really, IMHO. In Java, equality comparison is commonly implemented
using the following pattern:

<snip>

I have at least three problems with that approach.

First, I can think of a number of comparisons where I might want to
compare two objects of different types, and not simply a shared
subobject. For example, what if I want to compare two different
containers derived from the same abstract base class? It's not that
it can't be done, it's that it's not clear what's being done. Are we
comparing the sizes of the containers, their contents, their
capacities, the types of their elements, or some some other property?
If I want to compare different shapes, do I compare their areas, their
origins, or some identifier?

Second, there may be a few cases where it is obvious to everyone what
== means between objects of different types, but these areas of
agreement are distressingly rare. This particularly seems to be the
case with overloaded operators, where once you stray from
well-established idioms it's possible to get wildly different
interpretations. My favorite example of this is the following:

Container<int> c;
//...
c = 12;

What does that assignment mean? I'm sure that it means to set every
element of c to 12, but I've gotten very different interpretations
from competent, experienced programmers: set the first element to 12,
resize the container to size 12, etc. These are the same programmers
who will be writing derived classes that have to respect the base
class designer's concept of what == means.

Third, the use of potentially or several (runtime) capability queries
and virtual function calls to implement comparison is slow,
error-prone, and (please excuse the prejudice) not C++. It looks like
there are a few typos in the C++ implementation, in
eq_comparable::equals and in the eq_helper template, but the major
problem is the large amount of mechanism for what is typically a
simple operation.

It's not that the mechanism you descibe is not interesting or
potentially useful in some other context, it's that people might
actually use it to implement operator ==! IMLTHO I think it is almost
universally better to abandon operator == in complex cases like this
and just say precisely what you mean. The advantage of being able to
say

if( a == b )

rather than

if( sameContents( a, b) )

disappears if it's not clear what the first version means.

Steve

Steve Dewhurst
www.semantics.org



[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Back to top
Niklas Matthies
Guest





PostPosted: Fri Mar 05, 2004 11:49 pm    Post subject: Re: Why this does not work ?? (inheritance and virtual oper Reply with quote

On 2004-03-05 14:42, Stephen C. Dewhurst wrote:
Quote:
On 5 Mar 2004 05:35:39 -0500, Niklas Matthies wrote:
:
Not really, IMHO. In Java, equality comparison is commonly implemented
using the following pattern:
:
I have at least three problems with that approach.

First, I can think of a number of comparisons where I might want to
compare two objects of different types, and not simply a shared
subobject. For example, what if I want to compare two different
containers derived from the same abstract base class? It's not that
it can't be done, it's that it's not clear what's being done. Are we
comparing the sizes of the containers, their contents, their
capacities, the types of their elements, or some some other property?
If I want to compare different shapes, do I compare their areas, their
origins, or some identifier?

Of course. As with any function, you first have to specify its
purpose. In this case, you have to specify what it should mean for two
objects of a given type to "compare equal". The approach I cited is
not meant to provide any guidance with that. It is only meant to help
with objects of different types once you what have decided what
equality should mean within the individual types (which has to include
any base types if they provide equality comparison). And of course,
any derived types have to fullfill the contract promised by their base
types.

Quote:
Second, there may be a few cases where it is obvious to everyone what
== means between objects of different types, but these areas of
agreement are distressingly rare.

I probably wasn't too clear in my previous posting, but what I meant
to say there is that when you have two different types, then in all
likelyhood what you want is that '==' means the same as for the most
derived common subtype of the two types.

For example, if C and D both derive from B, and B provides equality
comparison for B objects, then c == d should have the same result
as c == b and b == d where b is the B sub-object of D and C,
respectively. In other words, if '==' is virtual in B, then the result
of c == d should not depend on whether you look at c and d as
instances of C and D or as instances of B (or any combination
thereof).

In the case that subtypes need some sort of equivalence that conflicts
with the equivalence for the supertypes, then either you don't have an
is-a relationship where you thought you had one (= design error), or
else you have to give these different equivalences different names or
implement them through adapters or by other non-intrusive means.

Quote:
Third, the use of potentially or several (runtime) capability queries
and virtual function calls to implement comparison is slow,
error-prone, and (please excuse the prejudice) not C++. It looks like
there are a few typos in the C++ implementation, in
eq_comparable::equals and in the eq_helper template, but the major
problem is the large amount of mechanism for what is typically a
simple operation.

You can wrap everything in a template so that you only have to provide
a typelist of the base types that should participate in the equality
comparison. Which base types should participate is a decision that
only the designer of the class can make, since it depends on what the
class is supposed to do.

Anyway, I believe that the situation where (a) you have public MI,
(b) more than one public base has an equality-comparison function
as part of its public interface and (c) these functions don't derive
from a single common base class that defines the meaning of the
equivalence, should be exceedingly rare in well-designed systems.
So you only get the complexity when you have a complex setting that
requires careful thought anyway.

:
Quote:
The advantage of being able to say

if( a == b )

rather than

if( sameContents( a, b) )

disappears if it's not clear what the first version means.

Completely agreed. I wasn't arguing about how you call the functions,
I was only talking about how to handle equivalence relations within
class hierarchies.

-- Niklas Matthies

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Back to top
James Kanze
Guest





PostPosted: Sat Mar 06, 2004 10:13 am    Post subject: Re: Why this does not work ?? (inheritance and virtual oper Reply with quote

Niklas Matthies <usenet-nospam (AT) nmhq (DOT) net> writes:

[...]
Quote:
I probably wasn't too clear in my previous posting, but what I meant
to say there is that when you have two different types, then in all
likelyhood what you want is that '==' means the same as for the most
derived common subtype of the two types.

In my experience, if you have two different types, most of the time,
they are not equal -- == should return false. The one major exception
I've seen is when you use different types to implement different
representations of what is logically the same type -- then == should
return true if the two different representations represent the same
value. The obvious example here is something like Numeric.

I don't think I've ever seen the case you describe, although I don't
doubt that it can exist. Typically, I'd guess that most hierarchies
consist of an abstract base (an interface in Java), and a number of
different implementations. You can't delegate to the abstract base,
because it doesn't have any data to compare.

Quote:
For example, if C and D both derive from B, and B provides equality
comparison for B objects, then c == d should have the same result as
c == b and b == d where b is the B sub-object of D and C,
respectively. In other words, if '==' is virtual in B, then the
result of c == d should not depend on whether you look at c and d as
instances of C and D or as instances of B (or any combination
thereof).

It all depends on what you are doing. To be frank, most of the time,
something like == is only relevant for value oriented classes, and value
oriented classes don't usually involve inheritance. In the cases where
they do, I've seen two major cases:

- Different derived classes represent different subsets (or
categories) of the values. Such cases are easy to handle -- anytime
the types are different, == returns false.

- Different derived classes implement different representations of the
values, and there is potentially some overlap, in that the same
value can be represented by several classes. In Java, BigInteger
and Double, for example, which both derive from the Numeric
interface, and which both can represent 0 (but there are values
which can only be represented by one or the other). To handle these
correctly, you really only have two choices : implement n^2
comparison functions, or define a "canonical" representation, which
can represent all possible values (perhaps a BigFloat), and convert
both sides to it.

Quote:
In the case that subtypes need some sort of equivalence that
conflicts with the equivalence for the supertypes, then either you
don't have an is-a relationship where you thought you had one (=
design error), or else you have to give these different equivalences
different names or implement them through adapters or by other
non-intrusive means.

Why wouldn't you have an is-a relationship?

--
James Kanze mailto:kanze (AT) gabi-soft (DOT) fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France +33 1 41 89 80 93

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Back to top
Niklas Matthies
Guest





PostPosted: Sun Mar 07, 2004 1:53 am    Post subject: Re: Why this does not work ?? (inheritance and virtual oper Reply with quote

On 2004-03-06 10:13, James Kanze wrote:
Quote:
Niklas Matthies <usenet-nospam (AT) nmhq (DOT) net> writes:
:
|> I probably wasn't too clear in my previous posting, but what I meant
|> to say there is that when you have two different types, then in all
|> likelyhood what you want is that '==' means the same as for the most
|> derived common subtype of the two types.

In my experience, if you have two different types, most of the time,
they are not equal -- == should return false.

Which does not contradict what I wrote above. It might not be obcious
at first, but what you describe happens to be a special case of what
I described. (Maybe it becomes more clear below.)

:
Quote:
|> In the case that subtypes need some sort of equivalence that
|> conflicts with the equivalence for the supertypes, then either you
|> don't have an is-a relationship where you thought you had one (=
|> design error), or else you have to give these different equivalences
|> different names or implement them through adapters or by other
|> non-intrusive means.

Why wouldn't you have an is-a relationship?

If a derived class requires a different kind of equality than its base
class, but at the same time has a need to use the same name for this
equality as the one used in the base class, then instances of the
derived class are-not instances of the base class with regard to that
equality comparison.

An "equality" conceptually means that for some specified set of
(unary) operations, if an object A compares equal to some object B,
then each of those operations yields the same behavior when applied
to A as when applied to B. In other words, A and B are substitutable
for each other in the context of these operations. Additionally, some
(possibly different) set of operations can be specified to yield
independent behavior for A and B when A != B (for instance if A and B
are keys into a map, the guarantee that the operation of removing A
from the map does not remove B from the map if A != B).

Any derived type who publicly inherits such an equality from its base
("inherits" as in interface inheritance) has to maintain these
invariants. This does _not_ mean that it cannot add to or modify the
base implementation. For example, a derived instance may happen to be
un-equivalent to each and every stand-alone base instance despite the
fact that it contains a base sub-object. And this does not constitute
an LSP violation, since the substitutability (the S in LSP) is defined
by the equality in the first place.

-- Niklas Matthies

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Back to top
Display posts from previous:   
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ Language (Moderated) All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2006 phpBB Group
SEO toolkit © 2004-2006 webmedic.