Topic: Why no contravariant argument types?


Author: drpizza@anti-flash.co.uk ("DrPizza")
Date: Fri, 22 Aug 2003 02:41:23 +0000 (UTC)
Raw View
Consider
struct Food
{};

struct Meat : Food
{};

struct Plants : Food
{};

struct Herbivore
{
 virtual void eat(Plants&);
};

struct Carnivore
{
 virtual void eat(Meat&);
};

struct Omnivore : Herbivore, Carnivore
{
 virtual void eat(Food&);
};


An Omnivore should be usable wherever one has a Herbivore or a Carnivore -- it
should be substitutable.  It's safe to do so.

But because contravariant arguments don't override, it isn't possible.
Omnivore::eat hides Herbivore::eat and Carnivore::eat instead.

Is there any reason for this omission?

--
Now Playing:  Lady Tom Meets Marc Van Linden - Motion (club mix) (D I G I T A
L L Y - I M P O R T E D - European Trance, Techno, Hi-NRG... we can't define
it!)



---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: m.collett@auckland.ac.nz (Matthew Collett)
Date: Fri, 22 Aug 2003 15:14:31 +0000 (UTC)
Raw View
In article <vkau7rrlo3o0d9@corp.supernews.com>,
 drpizza@anti-flash.co.uk ("DrPizza") wrote:

> struct Food
> {};
>
> struct Meat : Food
> {};
>
> struct Plants : Food
> {};
>
> struct Herbivore
> {
>  virtual void eat(Plants&);
> };
>
> struct Carnivore
> {
>  virtual void eat(Meat&);
> };
>
> struct Omnivore : Herbivore, Carnivore
> {
>  virtual void eat(Food&);
> };
>
> An Omnivore should be usable wherever one has a Herbivore or a Carnivore -- it
> should be substitutable.  It's safe to do so.
>
> But because contravariant arguments don't override, it isn't possible.
> Omnivore::eat hides Herbivore::eat and Carnivore::eat instead.
>
> Is there any reason for this omission?

Unlike covariant return types, contravariant arguments do not need
special-purpose language support, since you can implement them by simple
forwarding functions.  For example:

struct Omnivore: Herbivore, Carnivore
{
 virtual void eatAny(Food&);
 virtual void eat(Meat& meat) {eatAny(meat);}
 virtual void eat(Plants& plant) {eatAny(plant);}
};

or, if you really want to use the same function name:

struct Omnivore: Herbivore, Carnivore
{
 virtual void eat(Food&);
 virtual void eat(Meat& meat) {eat(static_cast<Food&>(meat));}
 virtual void eat(Plants& plant) {eat(static_cast<Food&>(plant));}
};

Best wishes,
Matthew Collett

--
Those who assert that the mathematical sciences have nothing to say
about the good or the beautiful are mistaken.          -- Aristotle

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: drpizza@anti-flash.co.uk ("DrPizza")
Date: Sun, 31 Aug 2003 18:10:09 +0000 (UTC)
Raw View
"Matthew Collett" <m.collett@auckland.ac.nz> wrote in message
news:m.collett-3D2FF6.20215722082003@lust.ihug.co.nz...

> Unlike covariant return types, contravariant arguments do not need
> special-purpose language support, since you can implement them by simple
> forwarding functions.  For example:
>
> struct Omnivore: Herbivore, Carnivore
> {
>  virtual void eatAny(Food&);
>  virtual void eat(Meat& meat) {eatAny(meat);}
>  virtual void eat(Plants& plant) {eatAny(plant);}
> };
>
> or, if you really want to use the same function name:
>
> struct Omnivore: Herbivore, Carnivore
> {
>  virtual void eat(Food&);
>  virtual void eat(Meat& meat) {eat(static_cast<Food&>(meat));}
>  virtual void eat(Plants& plant) {eat(static_cast<Food&>(plant));}
> };
>


But one might just as well argue that one doesn't need covariant return types
and that one should just keep the return type the same in the derived class.

Or that one doesn't need compiler-supported virtual functions at all, because
one can just build a v-table manually.  Or member functions, because one can
just pass "this" explicitly.  Or....

The compiler supports these things so that we can clearly express intent.  Why
not this one too?


--
Now Playing:  DJ Ciacomix - Friendship Rocks! CD1 (D I G I T A L L Y - I M P O
R T E D - European Trance, Techno, Hi-NRG... we can't define it!)



---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: m.collett@auckland.ac.nz (Matthew Collett)
Date: Tue, 2 Sep 2003 16:30:16 +0000 (UTC)
Raw View
In article <vkp13dj404ltee@corp.supernews.com>,
 drpizza@anti-flash.co.uk ("DrPizza") wrote:

> > Unlike covariant return types, contravariant arguments do not need
> > special-purpose language support, since you can implement them by simple
> > forwarding functions.
>
> But one might just as well argue that one doesn't need covariant return types
> and that one should just keep the return type the same in the derived class.

Not a good analogy, since this loses type information that is preserved
by the special-purpose language support.

> Or that one doesn't need compiler-supported virtual functions at all, because
> one can just build a v-table manually.

Not a good analogy, since in this case the workaround is not short and
simple but complicated and error-prone.

>Or member functions, because one can
> just pass "this" explicitly.

A much better analogy, and there are some who think that having special
syntax for member functions was indeed unnecessary.

> The compiler supports these things so that we can clearly express intent.  Why
> not this one too?

I'd say that a one-line inline forwarding function _was_ a fairly clear
expression of intent.

I also suspect that trying to provide special support for contravariant
arguments would run into ambiguities.  What happens when the programmer
wants to provide a new function with contravariant argument as well as
the inherited version?  Note that this is currently legal (indeed, it's
what the forwarding approach does), and the solution needs to avoid
breaking existing code.  What happens when the inherited argument has
multiple base classes, and the derived class provides overloads for more
than one?  Neither of these are a problem for covariant returns, since
you can't overload on return type anyway.

Best wishes,
Matthew Collett

--
Those who assert that the mathematical sciences have nothing to say
about the good or the beautiful are mistaken.          -- Aristotle

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]