Topic: Containers and Contravariance


Author: immel@flail.centerline.com (Mark Immel)
Date: 28 Oct 1994 15:37:01 GMT
Raw View
All --

  There has been some discussion here of allowing Collection<Derived> to
be used any where Collection<Base> can be.  This may seem like a good idea,
but it is fundamentally wrong in a "typed" language.  Consider the following
somewhat stylized example:

********************************************

struct Base{};
struct Derived: public Base {};

template <class T>
struct Container
{
  virtual void Add(const T& t) = 0; /* Add a new element to the container. */
  virtual T&   GetFirst() = 0;      /* Return the "first" element. */
};

void foo(Container<Base>& cb)
{
  cb.Add(*(new Base());
}

void bar()
{
  Container<Derived> cd;

  foo(cd); /* This call is not legal in the current language, but would be
       under the changes some have proposed. */

  Derived& d = cd.GetFirst(); /* What have I got now? */
}

******************************************

Mr. Schuenemann claims that he would prefer to allow "Collection<Derived>
conforms to Collection<Base>" in part "because it is consistent with the
typesystem."

But it is clear that is not; when we call cd.GetFirst() at the end of the
example we get back a Derived&.  But what we put in was a Base& so we're
in a heap of trouble.

This illustrates a common phenomenon in typing called contravariance.

Fundamentally the problem is that while a Derived "ISA" Base, a
Container<Derived> is *not* a Container<Base>!

-- Mark Immel
   immel@centerline.com


--


-- Mark Immel
   immel@centerline.com




Author: schuenem@Informatik.TU-Muenchen.DE (Ulf Schuenemann)
Date: 28 Oct 1994 20:10:16 GMT
Raw View
In article <IMMEL.94Oct28113702@flail.centerline.com>, immel@flail.centerline.com (Mark Immel) writes:
[..]
|> void foo(Container<Base>& cb)
|> {
|>   cb.Add(*(new Base());
|> }
|> void bar()
|> {
|>   Container<Derived> cd;
|>
|>   foo(cd); /* This call is not legal in the current language, but would be
|>        under the changes some have proposed. */
|>
|>   Derived& d = cd.GetFirst(); /* What have I got now? */
|> }
|> Mr. Schuenemann claims that he would prefer to allow "Collection<Derived>
|> conforms to Collection<Base>" in part "because it is consistent with the
|> typesystem."
|>
|> But it is clear that is not; when we call cd.GetFirst() at the end of the
|> example we get back a Derived&.  But what we put in was a Base& so we're
|> in a heap of trouble.

Damn, I realy forgot about what happens when WRITING to this container.
(i.e. putting a instance of the baseclass into it). When I said "I would
prefere .." was only thinking about READING.

|> This illustrates a common phenomenon in typing called contravariance.

At least I'm not alone :-)


(1) If I were pedantic, I could say: Dont mix references with values:

 void foo(Container<Base> cb) // Container cb as a value!
 {    // maybe we got a Container<Derived> with lots
   cb.Add(*(new Base()); // of Deriveds, but no problem to add a Base
 }
 void bar()
 {
   Container<Derived> cd;

   cb = foo(cd);  // when(!) copy-ctor is defined so that the whole
    // container is copied, cd is not changed by foo
   Derived& d = cd.GetFirst(); // => cd seems to be still empty at this point
 }
This only works when real valuesemantic is implemented - a fact that is not
checked by the compiler and is not representable by the C++ typesystem.
- But for my shame I must say my intended meaning actually was references.


(2) I still think Collection<Derived> d conforms to Collection<Base> in cases
where no Base is inserted into d and no Derived in d is overwritten by a Base.
This is a very specific restriction. C++ only alows a more general one
that prevents any modification of d: constness.

So I reduce my claim to:  `Collection<Derived>' conforms to `const Collection<Base>'

Can you agree?
This of course is less usefull than the original but wrong idea.


Ulf Schuenemann

--------------------------------------------------------------------
Ulf Sch   nemann
Institut f   r Informatik, Technische Universit   t M   nchen.
email: schuenem@informatik.tu-muenchen.de




Author: schuenem@Informatik.TU-Muenchen.DE (Ulf Schuenemann)
Date: 31 Oct 1994 13:18:19 GMT
Raw View
A Immedeate Followup Is Not A Good Followup
Or
The Pitfalls Of Contravariance/Covariance

The weekend I noticed that I had forgotten templatespecialization:

 void Container<Derived>::meth(Dervied& d) const // virtual
 {
 d.something_special_to_Dervied = something;
 }
 void foo(const Container<Base>& cb) // idea was: may get Container<Derived>
 {
 cb.meth(*(new Base()); // calls virtual
    // Container<Derived>::meth(Derived&) const
    // with a Base
 }
`Container<Derived> conforms to const Container<Base>' may crash when
a virtual method with parameters of type of the templateparameter
are specialized. Restricting this would be too awkward - so forget
this idea. Sorry for the confusion.


This `retreat' brings me back to arrays where my considerations started.
I believe 've learned now in this covarianceconfusion that can ONLY
work with arrays as they have no methods:

  Derived[n] conforms to  Base[n]

I wonder why:
 Dervied y;
 Dervied *(ds[3]) = {&y,&y,&y};
 Dervied *(ds2[3]) = ds;  // legal
 Base *(bs[3]) = {&y,&y,&y}; // legal
are legal, but
 Base *(bs2[3]) = ds;  // illegal
is not.

What did I forget this time?


Ulf Schuenemann

--------------------------------------------------------------------
Ulf Sch   nemann
Institut f   r Informatik, Technische Universit   t M   nchen.
email: schuenem@informatik.tu-muenchen.de