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