Topic: Proposed enhancement - 'thisclass' keyword for virtual function parameters


Author: ark@research.att.com (Andrew Koenig)
Date: 1998/05/22
Raw View
In article <6k0tpt$7m6@netlab.cs.rpi.edu>,
Oleg Zabluda  <zabluda@math.psu.edu> wrote:

> BTW, is there any reason why contravariant arguments weren't
> allowed in C++ at the same time covariant return types were
> allowed? Was there ever any discussion of that?

The main reason is that they're not terribly useful.
--
    --Andrew Koenig
      ark@research.att.com
      http://www.research.att.com/info/ark


      [ Send an empty e-mail to c++-help@netlab.cs.rpi.edu for info ]
      [ about comp.lang.c++.moderated. First time posters: do this! ]


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1998/05/22
Raw View
Oleg Zabluda wrote:
>
> Fergus Henderson <fjh@cs.mu.OZ.AU> wrote:
> : Oleg Zabluda <zabluda@math.psu.edu> writes:
>
> : >Markus Kohler <markus_kohler@bbn.hp.com> wrote:
> : >: Generally  your problem is related to covariant typing.
> : >: C++ allows only covariant return values. That means you can
return a
> : >: derived type in a subclass.
> : >: C++ does not allow covariant arguments :-(
> : >: Eiffel for example supports that
> : >
> : >I don't know Eiffel, but intend to learn it. Also, being a
> : >mathematician, I am not very familiar with cs lingvo. But,
> : >shouldn't it be _contravariant_ arguments?
>
> : That depends.  If you're asking about Eiffel, then no,
> : Eiffel does support covariant arguments.  If you're asking
> : about programming language design, then yes, arguments should be
> : contravariant -- IMHO, allowing covariant arguments is a really
> : bad idea.  I think the Eiffel design was a poor decision.
> : It would be foolish for C++ to allow covariant arguments.
>
> Right. Jeez. That's a major error in Eiffel. Foolish
> is the mildest way you can put it.
>
> BTW, is there any reason why contravariant arguments weren't
> allowed in C++ at the same time covariant return types were
> allowed? Was there ever any discussion of that?

I guess because it would have bad interaction with overloading.
Especially it could silently change the meaning ov a conforming program:

class A {};
class B: public A {};

class X
{
public:
  virtual void f(B) {}
};

class Y: public X
{
public:
  void f(A) { cout << "Y" << endl; }
};

class Z: public Y
{
public:
  void f(A) { cout << "Z" << endl; }
};

int main()
{
  Y* py=new Z;
  py->f(A());
}

Currently what happens is the following:
Y defines a new, nonvirtual member function void f(A), which overloads
(and hides) the virtual function void X::f(B). Z also defines a new
member function void f(A) - since Y::f(A) is not virtual, the new
member function Z::f(A) does not override Y::f(A). Now, in the main
program, f(A) is called with a pointer to Y, so since f(A) is not
virtual, Y::f(A) is called. The program therefore prints "Y".

With contravariant arguments, the following would happen:
Y defines void Y::f(A). Since B is derived from A, it overrides
X::f(B) due to contravariant argument types. Especially, since it
overrides a virtual function, it is virtual itself. Now Z overrides
the virtual function void Y::f(A). The pointer call in main now
resolves to Z::f(A) (since it's a virtual function), and therefore
the program prints "Z".

There are also other problems which again are caused by overloading:

class A {};
class B: public A {};
class C: public A {};

class X
{
public:
  virtual void f(B);
  virtual void f(C);
};

class Y: public X
{
public:
  void f(A); // which function does that override?
};

Note that those problems cannot occur with return types, since
you cannot overload a function on its return type.


However, I could think of a mechanism where you'd specify
the function which you override explicitly:

class A {};
class B: public A {};

class X
{
public:
  virtual void f(B);
  virtual void g(B);
};

class Y: public X
{
public:
  void f(A); // current meaning: new, unrelated function
  void g(A) overrides void X::g(B);
             // explicitly gives the function it overrides.
};

There could be a shorthand "overrides;" which means "overrides
the function with the same signature (maybe except for covariant
return type).

It could also allow compiler checks not possible today,
like in the following example:

class Base
{
public:
  virtual void do_something();
};

class Derived: public Base
{
public:
  void dosomething() overrides;
    // error: no overridable void dosomething() in base class
};

It also could be extended to give a natural renaming feature:

class Base1
{
public:
  virtual void f();
};

class Base2
{
public:
  virtual void f();
};

class Derived: public Base1, public Base2
{
public:
  void f1() overrides void Base1::f();
  void f2() overrides void Base2::f();
};

      [ Send an empty e-mail to c++-help@netlab.cs.rpi.edu for info ]
      [ about comp.lang.c++.moderated. First time posters: do this! ]


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: fjh@cs.mu.OZ.AU (Fergus Henderson)
Date: 1998/05/20
Raw View
Oleg Zabluda <zabluda@math.psu.edu> writes:

>Markus Kohler <markus_kohler@bbn.hp.com> wrote:
>: Generally  your problem is related to covariant typing.
>: C++ allows only covariant return values. That means you can return a
>: derived type in a subclass.
>: C++ does not allow covariant arguments :-(
>: Eiffel for example supports that
>
>I don't know Eiffel, but intend to learn it. Also, being a
>mathematician, I am not very familiar with cs lingvo. But,
>shouldn't it be _contravariant_ arguments?

That depends.  If you're asking about Eiffel, then no,
Eiffel does support covariant arguments.  If you're asking
about programming language design, then yes, arguments should be
contravariant -- IMHO, allowing covariant arguments is a really
bad idea.  I think the Eiffel design was a poor decision.
It would be foolish for C++ to allow covariant arguments.

--
Fergus Henderson <fjh@cs.mu.oz.au>  |  "I have always known that the
pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>  |  of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3        |     -- the last words of T. S.
Garp.

      [ Send an empty e-mail to c++-help@netlab.cs.rpi.edu for info ]
      [ about comp.lang.c++.moderated. First time posters: do this! ]
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Pete Becker <petebecker@acm.org>
Date: 1998/05/20
Raw View
Oleg Zabluda wrote:
>
> I don't know Eiffel, but intend to learn it. Also, being a
> mathematician, I am not very familiar with cs lingvo. But,
> shouldn't it be _contravariant_ arguments?

Only if you think type safety is important. <g>

      [ Send an empty e-mail to c++-help@netlab.cs.rpi.edu for info ]
      [ about comp.lang.c++.moderated. First time posters: do this! ]


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Oleg Zabluda <zabluda@math.psu.edu>
Date: 1998/05/21
Raw View
Fergus Henderson <fjh@cs.mu.OZ.AU> wrote:
: Oleg Zabluda <zabluda@math.psu.edu> writes:

: >Markus Kohler <markus_kohler@bbn.hp.com> wrote:
: >: Generally  your problem is related to covariant typing.
: >: C++ allows only covariant return values. That means you can return a
: >: derived type in a subclass.
: >: C++ does not allow covariant arguments :-(
: >: Eiffel for example supports that
: >
: >I don't know Eiffel, but intend to learn it. Also, being a
: >mathematician, I am not very familiar with cs lingvo. But,
: >shouldn't it be _contravariant_ arguments?

: That depends.  If you're asking about Eiffel, then no,
: Eiffel does support covariant arguments.  If you're asking
: about programming language design, then yes, arguments should be
: contravariant -- IMHO, allowing covariant arguments is a really
: bad idea.  I think the Eiffel design was a poor decision.
: It would be foolish for C++ to allow covariant arguments.

Right. Jeez. That's a major error in Eiffel. Foolish
is the mildest way you can put it.

BTW, is there any reason why contravariant arguments weren't
allowed in C++ at the same time covariant return types were
allowed? Was there ever any discussion of that?

Oleg.
--
Life is a sexually transmitted, 100% lethal disease.

      [ Send an empty e-mail to c++-help@netlab.cs.rpi.edu for info ]
      [ about comp.lang.c++.moderated. First time posters: do this! ]


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Marc Girod <girod@stybba.ntc.nokia.com>
Date: 1998/05/27
Raw View
>>>>> "ark" == Andrew Koenig <ark@research.att.com> writes:

ark> In article <6k0tpt$7m6@netlab.cs.rpi.edu>,
ark> Oleg Zabluda  <zabluda@math.psu.edu> wrote:

>> BTW, is there any reason why contravariant arguments weren't
>> allowed in C++ at the same time covariant return types were
>> allowed? Was there ever any discussion of that?

ark> The main reason is that they're not terribly useful.

A bit short, don't you think so?

I have found myself trying to circumvent this is the past.
My problem was with template classes building up "dual hierarchies".

Say you have the classic:

class OBase;
class VBase {
  virtual void go(OBase&) = 0;
  virtual ~VBase() {}
};
template <class T> Vehicle: virtual private VBase {
    typedef typename T::Operator Op;
  // I'd have liked to dedicate this interface to Op through
  // friendship, but this is a different story...
  public:
    virtual void go(Op&) = 0;
};
// I leave the remaining half and the implementations

In this kind of example, one (at least I :-) can argue that the lack
of support for contravariant arguments builds up a "lack of
orthogonality" problem, preventing me from moving from a non-template
definition of Vehicle to a template one (in order to tighten type
awareness in my program).

I admit right now that I would not be out of the water just with
contravariant arguments... This would then furiously look soon like a
multiple dispatch problem (see the posting by Christopher Eltschka).

Anyway, I'd argue that contravariant arguments could be "terribly
useful" (and that the kind of construction above is --mind you--
extremely widely applicable).

Best Regards!

--
Marc Girod                Valimo 1/2         Voice:  +358-9-511 63331
Nokia Telecommunications  P.O. Box 315       Mobile: +358-40-569 7954
NWS/NMS/NMS for Data      00045 NOKIA Group  Fax:    +358-9-511 63310
                          Finland            marc.girod@ntc.nokia.com

      [ Send an empty e-mail to c++-help@netlab.cs.rpi.edu for info ]
      [ about comp.lang.c++.moderated. First time posters: do this! ]
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Marc Girod <girod@stybba.ntc.nokia.com>
Date: 1998/05/28
Raw View
Ahum...
A somewhat embarrassed correction to my yesterday followup to Andrew
Koenig (thanks to Robert Martin).

>>>>> "MG" == Marc Girod <girod@stybba.ntc.nokia.com> writes:

MG> Anyway, I'd argue that contravariant arguments could be "terribly
MG> useful"

This was about covariant arguments, and thus, out of scope.

Sorry.

--
Marc Girod                Valimo 1/2         Voice:  +358-9-511 63331
Nokia Telecommunications  P.O. Box 315       Mobile: +358-40-569 7954
NWS/NMS/NMS for Data      00045 NOKIA Group  Fax:    +358-9-511 63310
                          Finland            marc.girod@ntc.nokia.com


      [ Send an empty e-mail to c++-help@netlab.cs.rpi.edu for info ]
      [ about comp.lang.c++.moderated. First time posters: do this! ]


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Oleg Zabluda <zabluda@math.psu.edu>
Date: 1998/05/19
Raw View
Markus Kohler <markus_kohler@bbn.hp.com> wrote:
: Generally  your problem is related to covariant typing.
: C++ allows only covariant return values. That means you can return a
: derived type in a subclass.
: C++ does not allow covariant arguments :-(
: Eiffel for example supports that

I don't know Eiffel, but intend to learn it. Also, being a
mathematician, I am not very familiar with cs lingvo. But,
shouldn't it be _contravariant_ arguments? That is, given
"pseudo C++":

class Base {
  public:
    virtual A* f(X*);
};

class Derived : public Base {
  public:
    virtual B* f(Y*);
};

Derived::f() overrides Base::f() iff B* is convertible to A*
(covariancy), and X* is convertible to Y* (contravariancy).

Oleg.
--
Life is a sexually transmitted, 100% lethal disease.

      [ Send an empty e-mail to c++-help@netlab.cs.rpi.edu for info ]
      [ about comp.lang.c++.moderated. First time posters: do this! ]
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: Markus Kohler <markus_kohler@bbn.hp.com>
Date: 1998/05/12
Raw View
Dylan Nicholson wrote:
>
> Hi
>
> I had an idea recently that partially solves a particularly annoying problem
> with C++, that is, the inability to successfully virtualize functions that
> take as parameters references to other objects of the same class - eg I
> can't do
>
> class Base
> {
>      int i;
> public:
>      Base(int n) { i = n; }
>      virtual Base& operator = (const Base& rhs)
>      {
>          i = rhs.i;
>          return *this;
>      }
> };
>
> then in a Derived class, override the operator = function so that
> the class of 'rhs' is the Derived class, not the Base class.
>
> My solution is to introduce a 'thisclass' keyword, thisclass standing
> for the type of the object being defined, so the above would read:
>
> class Base
> {
>      int i;
> public:
>      Base(int n) { i = n; }
>      virtual thisclass& operator = (const thisclass& rhs)
>      {
>           i = rhs.i;
>           return *this;
>      };
> };
>
> If you wished to override operator = in a Derived class, it would be
> trivial:
>
> class Derived : public Base
> {
>      int j;
> public:
>      Derived() { j = 0 };
>      virtual thisclass& operator = (const thisclass& rhs)
>      {
>           ((Base&)*this) = rhs;
>           j = rhs.j;
>           return *this;
>      }
> };
>
> The compiler would only allow calls to a virtual member defined like this
> if it knew at compile time that all parameters were of the most derived class
> of the class of invoking object. If one or more of them were of a lesser
> type, it would probably be forced to statically bind to the member defined
> for the class of the invoking object (although this could be semantically
> problematic, maybe a warning would be in better order).
> This should give an idea of what I have in mind:
>
> void DoSomething1(Base& obj1, Derived& obj2)
> {
>     ...
>     obj1 = obj2;  // virtual operator = is called, because obj2 is a
> Derived
>                   // thus if obj1 is really a Derived, Derived::operator
> =
>                   // will be called.
>     ...
> }
>
> void DoSomething2(Base& obj1, Base& obj2)
> {
>     ...
>     obj1 = obj2; // Base::operator = is forcibly called, because obj2 is not
>                  // known to be Derived at compile time.  A warning might
>                  // be displayed.
> }
>
> int main()
> {
>     Derived d1(1);
>     Derived d2(2);
>     ...
>     DoSomething1(d1, d2);
>     DoSomething2(d1, d2);
>     ...
>     return 0;
> }
>
> If we stuck to compile time binding like this, its usage would be quite
> restrictive, because adding another class derived from either Base or Derived
> would mean that the virtual function could not be safely called in DoSomething1.
> Alternatively, I could be truly radical and suggest that at run-time it
> is negotiated which operator to call based on the type of the parameters
> passed in.  The simplest thing would be I guess to compare the values of the
> virtual function pointers (assuming we're using vtbls - how many compilers
> don't???) on the lhs and rhs objects and if they are same, it is safe to call
> the virtual function for the lhs object.  A more complex (but perhaps more
> useful) approach would be to determine the 'lowest common demoninator' class
> (ie the one from which all objects are derived), and call its member, however
> this would require a fair bit of run-time type info (although I suspect not a
> lot more than what is used in exception handling code).
>
> Anyway, I'm interested in what people think...
>

You might take a look at Luca Cardelli's home page :
http://www.luca.demon.co.uk/.
You can find there a bunch of excellent documents about this and other
problems
with type systems. Especially "On Binary Methods" should be interesting
for you.

I also have another article from the web which exactly describes what
you propose.
Unfortunately I lost the reference :-(
The author is Kim B. Bruce Williams College and the title is "Typing in
object-oriented
languages: Achieving expressiveness and safety".


Generally  your problem is related to covariant typing.
C++ allows only covariant return values. That means you can return a
derived type in a subclass.
C++ does not allow covariant arguments :-(
Eiffel for example supports that


Markus

--
[This signature is intentionally left blank.]


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1998/05/04
Raw View
Dylan Nicholson wrote:
>
> Hi
>
> I had an idea recently that partially solves a particularly annoying problem
> with C++, that is, the inability to successfully virtualize functions that
> take as parameters references to other objects of the same class - eg I
[excess quoting deleted --hlh]

You can already do what you want today:

class Base
{
  int i;
public:
  Base(int n): i(n) {}
  virtual Base& operator=(const Base& rhs)
  {
    i = rhs.i;
    return *this;
  }
};

class Derived: public Base
{
  int j;
public:
  Derived(): Base(3), j(0) {}
  virtual Derived& operator=(const Derived& rhs)
  {
    Base::operator==((Base&)rhs);
    j=rhs.j;
  }
  virtual Derived& operator=(const Base& rhs)
  {
    Derived* rhs_d=dynamic_cast<Derived*>(&rhs);
    if(rhs_d !=NULL)
      return (*this)=(*rhs_d);  // calls Derived& operator=(const
Derived&)
    else
    {
      Base::operator==(rhs);
      return *this;
    }
  }
};

I admit it's more typing, but it does the thing you want:

If the left side is a derived, and the right side is as well,
Derived& Derived::operator=(const Derived&) is called. If the left side
is a base, Base& Base::operator=(const Base&) is called. If the left
side is a Derived, but the right side is a Base, the Base part is
assigned (via Base& Base::operator=(const Base&), and the Derived only
part is untouched (alternatively you could set it to other values,
like default values, in the else block of Derived::operator=(const
Base&)
if this makes more sense in your case). It even works for different
derivations from Base, since Derived would treat a Derived2 which
does derive from Base, not Derived, exactly the same as if it were
a Base (i.e. copy the Base part, and treat its own Derived only part
in a consistant manner, written in Derived).

      [ Send an empty e-mail to c++-help@netlab.cs.rpi.edu for info ]
      [ about comp.lang.c++.moderated. First time posters: do this! ]


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]