Topic: abstract functions of abstract classes


Author: jason@cygnus.com (Jason Merrill)
Date: Thu, 28 Jul 1994 21:15:01 GMT
Raw View
>>>>> Gary Thompson <garyt@resumix.portal.com> writes:

> struct A {
>   virtual A func() = 0;
>  };

> struct B: public A {
>   B func()
>  {...}
>  };

A common implementation of value return is to pass in a pointer to a block
of memory of the appropriate size.  The return value is then constructed in
that space.  If A and B are of different sizes, polymorphism for func ()
would break that implementation, and most others (all?).

Jason




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Fri, 29 Jul 1994 08:06:01 GMT
Raw View
In article <JASON.94Jul28141501@deneb.cygnus.com> jason@cygnus.com (Jason Merrill) writes:
>>>>>> Gary Thompson <garyt@resumix.portal.com> writes:
>
>> struct A {
>>   virtual A func() = 0;
>>  };
>
>> struct B: public A {
>>   B func()
>>  {...}
>>  };
>
>A common implementation of value return is to pass in a pointer to a block
>of memory of the appropriate size.  The return value is then constructed in
>that space.  If A and B are of different sizes, polymorphism for func ()
>would break that implementation, and most others (all?).

 Rot. Exactly this mechanism will work just fine
for covariant value returns if you organise your virtual
dispatches correctly. If you dont, you will find
covariant returns fail for multiple inheritance _anyhow_.

 So how does covariant value return work?
Easy: The call indeed allocates space for A. But the dispatch
is to a stub function that allocates space for a B, calls the
virtual function with a pointer to it, then copies the
initialised B value up to the space allocated for the A.

 Works fine. And the same stub function is required
to perform covariant pointer return adjustments. There's no
different: in BOTH cases an upwards conversion is required
to an expected type.
--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,
        81A Glebe Point Rd, GLEBE   Mem: SA IT/9/22,SC22/WG21
        NSW 2037, AUSTRALIA     Phone: 61-2-566-2189




Author: ball@cygany.Eng.Sun.COM (Mike Ball)
Date: 29 Jul 1994 16:25:02 GMT
Raw View
In article 94Jul28141501@deneb.cygnus.com, jason@cygnus.com (Jason Merrill) writes:
> >>>>> Gary Thompson <garyt@resumix.portal.com> writes:
>
> > struct A {
> >   virtual A func() = 0;
> >  };
>
> > struct B: public A {
> >   B func()
> >  {...}
> >  };
>
> A common implementation of value return is to pass in a pointer to a block
> of memory of the appropriate size.  The return value is then constructed in
> that space.  If A and B are of different sizes, polymorphism for func ()
> would break that implementation, and most others (all?).

Well, actually, no, it wouldn't.  Though there are certainly implementation
techniques that would break with this ability, there are others that wouldn't.

One implementation that works just fine effectively generates two versions of
func, one that returns A and another that returns B.  The one returning A directly
overrides A::func, and is the one that gets called if you call the function
through an A*.  The other is called if you call the function through B*.

Of course you don't actually need two copies of the functions, as two entry points
would do as well, but that's just an optimization.  Other optimizations might
be used for other sets of return values.

This isn't to say that there are no reasons to prohibit or limit the differences
in return types from virtual function, but as long as the derived class return
type can be converted to the base class return type the reason isn't implementation.

By the way, this is now legal for returns of pointers, say A* and B*, or of references,
say A& and B&, but not for say, double and float.

-Mike Ball-







Author: olaf@cwi.nl (Olaf Weber)
Date: Fri, 29 Jul 1994 12:24:05 GMT
Raw View
My previous post in this thread had a rather aggressive tone, for
which I apologize.

The problems raised in it could be made tractable by using multi-
methods, a language extension mentioned by Stroustrup in "The Design
and Evolution of C++".  He also writes that their lack is a defect in
C++, so they might actually make it into the language one day.  The
syntax used in the example below is borrowed from that book.

The idea behind multi-methods is to let more than one argument of a
call determine which function is to be called _at_run-time_.

Example:

 // Multi-methods as class members, not C++

 struct A {
  virtual void f(virtual A const &);  // Multi-method.
  virtual A& g(virtual A const &);
 };

 struct B: public A {
  void f(B const &);
  B& g(B const &);
 };

 struct C: public A {
  void f(C const &);
  void f(B const &);
 };

 B b1, b2;
 C c1, c2;

 b1.f(b2);      // OK, call `B::f(B const &)'.
 b1.f(c1);      // OK, call `A::f(A const &)'.
 c1.f(c2);      // OK, call `C::f(C const &)'.
 c1 f(b1);      // OK, call `C::f(B const &)'.

 A& ab1 = b1;
 A& ab2 = b2;
 A& ac1 = c1;
 A& ac2 = c2;

 ab1.f(ab2);    // OK, call `B::f(B const &)'.
 ab1.f(ac1);    // OK, call `A::f(A const &)'.
 ac1.f(ac2);    // OK, call `C::f(C const &)'.
 ac1.f(ab1);    // OK, call `C::f(B const &)'.

The mechanism is powerful, if possibly confusing.  There are some
catches though.  You cannot have pure virtual multi-method member
functions: it is impossible to guarantee that they won't be called.

Combining multi-methods with a virtual function with differnt return
types in dereived classes (as with `A::g()' and `B::g') leads to
another problem:

 void f(A const &);
 void f(B const &);

 f(ab1.g(ab2));

Which `f()' is called should depend on the _run-time_ types of `ab1'
and `ab2'.  Probably the best way to handle cases like this is to
require that `f()' is itself a multi-method as well, like this:

 void f(virtual A const &);

It is obvious that if multi-methods are ever seriously considered, a
lot of attention will have to go to the way it interacts with existing
language features, lest we end up with something that is far more
costly to implement than foreseen.

-- Olaf Weber




Author: maxtal@physics.su.OZ.AU (John Max Skaller)
Date: Sat, 30 Jul 1994 15:42:04 GMT
Raw View
In article <31bagu$q3k@engnews2.Eng.Sun.COM> ball@cygany.Eng.Sun.COM writes:
>
Covariant returns ...

>By the way, this is now legal for returns of pointers, say A* and B*, or of references,
>say A& and B&, but not for say, double and float.

 Silly. It should be allowed if, and only if,
there is an unambiguous implicit conversion (or binding) from the return
type of the overriding virtual to the base virtual.
(Except for overriding a reference by a value return which I think
always leads to a dangling reference)

 That means that "covariant" would be a misnomer.
In particular, for pointers to members, it would be
_contravariant_. Did anyone think of allowing contravariant
pointer to member returns?

--
        JOHN (MAX) SKALLER,         INTERNET:maxtal@suphys.physics.su.oz.au
 Maxtal Pty Ltd,
        81A Glebe Point Rd, GLEBE   Mem: SA IT/9/22,SC22/WG21
        NSW 2037, AUSTRALIA     Phone: 61-2-566-2189




Author: olaf@cwi.nl (Olaf Weber)
Date: Thu, 28 Jul 1994 13:30:13 GMT
Raw View
In article <1994Jul28.093922.11804@leeds.ac.uk>, garyt@resumix.portal.com (Gary Thompson) writes:

> In article 5A4@cwi.nl, olaf@cwi.nl (Olaf Weber) writes:

>> In article <1994Jul27.103403.5291@leeds.ac.uk>, garyt@resumix.portal.com (Gary Thompson) writes:

>> > I have an abstract base class, an ABC, with abstract functions. what
>> > I want to know is why can't abstract functions return abstract
>> > classes of the same type as the abstract class or one of it's base
>> > classes from a virtual function.

[ Example illustrating rules of ARM 19.10.2 deleted ]

> you miss my point, i think.

The original question wasn't very readable, IMHO, but I did get the
point, again IMHO, even if I didn't address the reasons why the rules
are the way they are.

First of all, you should realise that the rules should be the same for
abstract classes as well as for concrete ones.  Otherwise you're
introducing unneeded complications.  So the fact that the base class
is abstract is not really relevant here.

> For an ABC to be a pure interface this also has to be possible:

> struct A {
>   virtual A func() = 0;
>  };

> struct B: public A {
>   B func()
>  {...}
>  };

Illegal by the current rules.  It was proposed (or so I hear) but
rejected (I don't know why).

> and this

> struct A {
>   virtual A operator +(const A&) = 0;
>  };

> struct B: public A {
>   B operator + (const B&)
>  {...}
>  };

Dangerous ground here.  The arguments types do not match, which means
that the operator in the derived class _hides_ the virtual operator in
the base class, instead of overriding it.  This means that B is an
abstract class.

Apart from the fact that some code might actually depend on such
hiding (bad practice IMO), there is a good reason not to allow this,
see below.

> though this should obviously be illegal

> struct A {
>   virtual A operator +(const A&) = 0;
>  };

> struct B: public A {
>   A operator + (const B&) //non abstract function return abstract type
>  {...}
>  };

This is indeed illegal for the reason you give.  But if A weren't an
abstract class, then again we would have that `B::operator(B const&)'
hides the operator in the base class, instead of overriding it.

> otherwise ABC's are rather hobbled in what they can act as interfaces for

Perhaps they are hobbled because of this.  However, there are good
reasons why the language is like this.  Consider the following example
in the light of the rules you'd like to have.

 struct A {
  virtual void f(A const&) = 0;
  virtual A g() = 0;
 };

 struct B: public A {
  void f(B const&) { /* ... */ }
  B g() { /* ... */ }
 };

 struct C: public A {
  void f(C const&) { /* ... */ }
  C g() { /* ... */ }
 };

 B b1, b2;
 C c1, c2;

 b1.f(b2);      // OK
 c1.f(c2);      // OK
 b2 = b1.g();   // OK
 c2 = c1.g();   // OK

 b1.f(c1);      // Illegal
 c2 = b2.g();   // Illegal

These particular cases could be caught at compile time.

 A& a1 = b1;
 A& a2 = c1;

 a1.f(a2);      // Illegal
 a2 = a1.g();   // Illegal

These cases cannot (in general) be caught at compile time.  Moreover,
given the purpose of having abstract classes in first case, they are
quite likely to occur.

-- Olaf Weber




Author: garyt@resumix.portal.com (Gary Thompson)
Date: Thu, 28 Jul 1994 09:39:22 GMT
Raw View
In article 5A4@cwi.nl, olaf@cwi.nl (Olaf Weber) writes:
> In article <1994Jul27.103403.5291@leeds.ac.uk>, garyt@resumix.portal.com (Gary Thompson) writes:
>
> > I have an abstract base class, an ABC, with abstract functions. what
> > I want to know is why can't abstract functions return abstract
> > classes of the same type as the abstract class or one of it's base
> > classes from a virtual function.
>
> The following has been made legal:
>
> struct A {
>  virtual A *func() = 0;
> };
>
> struct B: public A {
>  B *func();
> };
>
> And it is also legal for references.  This can only be done for the
> return type though.  (ARM, chapter 19, section 10.2)
>


you miss my point, i think. For an ABC to be a pure interface this also has to be possible:


struct A {
  virtual A func() = 0;
 };

struct B: public A {
  B func()
 {...}
 };

and this

struct A {
  virtual A operator +(const A&) = 0;
 };

struct B: public A {
  B operator + (const B&)
 {...}
 };


though this should obviously be illegal

struct A {
  virtual A operator +(const A&) = 0;
 };

struct B: public A {
  A operator + (const B&) //non abstract function return abstract type
 {...}
 };

otherwise ABC's are rather hobbled in what they can act as interfaces for

gary