From: warsaw@nlm.nih.gov (Barry A. Warsaw)
Newsgroups: comp.lang.c++
Subject: downcasting and "contravariants"?
Date: 16 Oct 92 17:09:47 GMT
Reply-To: warsaw@nlm.nih.gov (Barry A. Warsaw)
Organization: Century Computing, Inc.


>>>>> "Richard" == Richard M. Hartman <hartman@ulogic.UUCP> writes:

    Richard> I do not have heterogenous lists, but in order to
    Richard> implement a StudentList I have to derive from List, which
    Richard> returns Object * (StudentObj is derived from Object, and
    Richard> can thus be placed on an NIH List).  When I get a Student
    Richard> from the List it is an Object * and I have to cast to
    Richard> StudentObj *.  I know that all members of the list are
    Richard> going to be StudentObj, but I still have to do the stupid
    Richard> downcast because I am using SOMEONE ELSE'S List class as
    Richard> a base class.

    Richard> If I were doing my OWN stuff (and my compiler supported
    Richard> templates) I could make use of some of this discussion.

>>>>> "Nikki" == Nikki Locke <nikki@trmphrst.demon.co.uk> writes:

    Nikki> If you don't have templates, downcasting appears to be the
    Nikki> only way to achieve what you (and many others) want.
    Nikki> However, you need to watch out for some pitfalls.

    Nikki> If you publicly derive your StudentObjList class from the
    Nikki> NIH List, you allow the StudentObjList to be treated as a
    Nikki> List. This means some code could add some other kind of
    Nikki> Object to your List. When you retrieve this unwanted
    Nikki> Object, you will have problems - you will cast it to a
    Nikki> StudentObj when it isn't one, giving the famous "undefined
    Nikki> behaviour".

    Nikki> What you need to do is to PRIVATELY derive from List, so
    Nikki> this can' happen. This means you will need to redefine all
    Nikki> the List methods in your derived class, but these
    Nikki> redefinitions can usually be inline, so there is no
    Nikki> performance hit.

I just wanted to come back and talk about this a bit more. I know
there's been discussion on a concept which I think was referred to as
contravariants/covariance, but those articles have been expired.

My understanding is that the basic problem of contravariance in C++ is
most evident when you attempt to use 3rd party libraries that provide
collections based on a canonical inheritance hierarchy (as opposed to
a forest-like template implementation).  Its been suggested that
templates are the way to solve the contravariant problem, and I think
it would, for most cases.  However, I think it would be in C++'s
interest to also allow covariant definition of virtual methods.
Here's why:

I don't want to write a Set class, I want to buy it off the shelf. A
year (or two) from now, I can perhaps purchase a production quality
templatizing compiler on all my supported platforms, but I cannot
currently do this, so a template solution is not an option.  Using
macro-based generics is also not an option, perhaps due to the
complexity of debugging such constructs. I'm left with the option
(today), of buying a canonical-object class library.  This locks me
into a type-unsafe downcast mechanism.

Note that I may also decide on a canonical-object library for another
reason: persistence.  I'm not saying persistence isn't available in
template solutions (perhaps through MI), but the advantages of
inheriting persistence seem clear to me, and the availability of a
canonical-class tree for collections which gives me persistance for
free might also drive my selection, even if I *had* templates available.

Anyway the point is that adding covariance to the language solves most
of my downcast problems.  For example, I could privately/protectedly
(?!) inherit from the library's Set class (to prohibit unsafe
upcasting back to a Set), then define the virtuals in MySet's public
interface to take only Object derived pointers:

class SomeCommercialToolkitObject
{ /* ... */ };

class SomeCommercialToolkitSet : public SomeCommericalToolkitObject
{
public:
	// ...
	SomeCommericalToolkitObject* insert( SomeCommercialToolkitObject* )
};

class SomeCommercialToolkitSetIterator
{
public:
	SomeCommercialToolkitSetIterator( SomeCommericalToolkitSet& );
	SomeCommercialToolkitObject* operator()();
	// ...
};

class Doodad : public SomeCommercialToolkitObject
{
public:
	// not inherited from SomeCommercialToolkitObject
	virtual void do_to_dad();
};

class Dooson : public Doodad
{
public:
	virtual void do_to_dad();
};

class DoodadSet : private SomeCommercialToolkitSet
{
public:
	Doodad* insert( Doodad* );
private:
	// to allow upcast to private base
	friend class DoodadSetIterator;
};


class DoodadSetIterator : private SomeCommercialToolkitSetIterator
{
public:
	DoodadSetIterator( DoodadSet& ds )
		: SomeCommercialToolkitSetIterator(ds)
	{};
	Doodad* operator()() {
		return (Doodad*)SomeCommercialToolkitSetIterator::operator()();
	};
};


If this were legal C++, then I think my downcasting problem would be
solved (appol. for any typos ;-).  I want to collect Doodad's, I use a
DoodadSet.  I want to iterate through the DoodadSet, I use a
DoodadSetIterator, which I can guarantee (through the private
inheritence + friendships) only contain Doodad or Doodad-derived
objects.  Thus I'm perfectly safe calling do_to_dad() on the return
ptr of DoodadSetIterator::operator().

Above, I said "If this were legal C++".  Of course, its not.
Interestingly enough (and here we get into compiler dependancies),
cfront (2.1) will *almost* allow me to do this, albeit with warnings.
As a side note, the generation of these warnings may be useful.  IMO,
A "covariant-fluent" compiler would only generate these warnings where
appropriate.  The only place where cfront chokes and produces an error
instead of a warning is on the operator() method.  Obviously it treats
operators different from other methods in this respect.  It shouldn't.

Comments anyone?  I maintain that even with the ubiquitous
availability of templates, covariant support is a worthwhile pursuit.
I'd be interested to know if there are any thoughts on adding this
feature to the language -- LMNNK! (Look, Ma, no new keywords!).  Also,
since at least one compiler seems to almost support the feature
anyway, what complications if any (perhaps fatal enough to doom this
feature) lurk beneath the sheets?  Does anybody else think that even
with templates, we need this in C++?
Newsgroups: comp.lang.c++