Topic: Covariant return types for virtual functions
Author: comeau@panix.com (Greg Comeau)
Date: 1999/06/01 Raw View
In article <374D0E2F.2887@eae.com> neppert.gernot@eae.com writes:
>Stanley Friesen [Contractor] wrote:
>> The compiler inserts the adjustment "between" the return from the derived
>> class function and the assignment to the base class pointer.
>>
>> One way of doing this is arranging it so that a call to the derived
>> function via a base pointer is done thorugh a "stub" function that acts
>> somewhat like this:
>>
>> Derived * Derived::func();
>> Base *Base::stub()
>> {
>> return Derived::func();
>> }
>
>Ok, that convinces me. Thanks!
>But it also shows that the compiler goes to great lengths to simulate
>the "good old days" when all overriding virtual functions had to have
>the same return types. The necessary additional level of indirection is
>also a performance issue. Is this rarely used feature (covariant return
>types) really worth the trouble?
This is not a huge feature in the grand scheme of things. Nonetheless,
the committee did not make this change lightly and it took a few papers
and meetings to get done as I recall it. Anyway, it's only an issue if
you use it, and certain libraries definitely benefit from this features.
You must also compare this against any performance penalties of not
having this feature (via hand written code to do it), not to mention the
(minor) code maintenance issues.
- Greg
--
Comeau Computing, 91-34 120th Street, Richmond Hill, NY, 11418-3214
Producers of Comeau C/C++ 4.2.38 -- New Release! We now do Windows too.
Email: comeau@comeaucomputing.com / Voice:718-945-0009 / Fax:718-441-2310
*** WEB: http://www.comeaucomputing.com ***
---
[ 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: Martin von Loewis <loewis@informatik.hu-berlin.de>
Date: 1999/05/29 Raw View
Ewert_Ahr._Electronic_GmbH@t-online.de (Gernot) writes:
> But I've gotten curious: How *could* it possibly do this?
On your example, there is a simple implementation approach: All
implementations implementations of foo return a Base*. If some caller
expects a Derived*, the compiler must dynamic_cast to Derived*. That
is:
Base* orig = new Base;
Base* target = orig->foo(); // Everything is fine, foo returns Base*
Derived *dorig = new Derived;
Base* orig = dorig
Base* target = orig->foo(); // Same thing, Derived::foo also returns Base*
Derived *dtarget = dorig->foo();
// Here, foo returns Base*. The compiler knows that the dynamic result
// must be Derived*, so it implicitly generates
dtarget = dynamic_cast<Derived*> (dorig->foo());
Of course, this approach fails if the covariant return type is not a
polymorphic type:
struct BResult{};
struct Inter1{int i;};
struct DResult:Inter1,virtual BResult{int k;}
struct Base{
virtual BResult*foo();
};
struct Derived:Base{
virtual DResult *foo();
};
In that case, a caller of Derived* ->foo cannot dynamic_cast the
resulting Base* to Derived*. In addition, it cannot static_cast,
either, because BResult is a virtual base of BResult.
I'm not aware of a compiler that actually implements this, but a
potential implementation approach would be:
- All virtual functions receive an additional implicit argument (in
addition to the this-pointer). This argument is the type_info of the
static type that the caller expects
- The virtual function first produces in the static type of the
virtual function. It then compares this type with the type required
by the calling function. This must be a base type of the actual
type, so the compiler has static knowledge about all potential base
types. The virtual function detects what the required type is, and
converts the result pointer accordingly.
This, of course, is a significant overhead to virtual functions, and
one that is probably incompatible with most compiler's current
approach to virtual functions.
There is a number of alternative approaches available, but I'm not
aware of an alternative approach that works in every case on most
processor architectures.
Regards,
Martin
---
[ 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: Ron Natalie <ron@sensor.com>
Date: 1999/05/27 Raw View
Gernot wrote:
>
> Every working implementation I know of will evaluate the expression
> "&der" to either the adress of the "B1" suboject or the "B2" subobject.
No, it evaluates the exression &der to be of type Derived*.
It just knows how to convert it when necessary (such as when
you assign it to a variable of type B1*) to the base class.
This conversion will involve adjusting the address to yield
the location of the embedded base class members.
> *But* if you have declared a virtual function to return a "B1*"
> and then later override it to return the address of a "Derived"
> instance, you have a different situation. This pointer can be and will
> be treated as a "B1*" without any adjustments. When the compiler
> generates code to use this pointer, it doesn't know (and doesn't need to
> know!) anything of classes "B2" and "Derived". Thus accessing members of
> class "B1" via this pointer will fail... Or am I missing something?
Yes, just as the virtual linkage needs to adjust the "this" pointer
between the base class object and the derived object, so must it
do so with the return value.
---
[ 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: "Bill Wade" <bill.wade@stoner.com>
Date: 1999/05/27 Raw View
Gernot wrote in message <374BBBA3.1A62@eae.com>...
>*But* if you have declared a virtual function to return a "B1*"
>and then later override it to return the address of a "Derived"
>instance, you have a different situation. This pointer can be and will
>be treated as a "B1*" without any adjustments. When the compiler
>generates code to use this pointer, it doesn't know (and doesn't need to
>know!) anything of classes "B2" and "Derived". Thus accessing members of
>class "B1" via this pointer will fail... Or am I missing something?
I believe Stanley's reply is correct, but I had some trouble understanding
it. Think of it this way:
You write
struct Base { virtual Base* func(); };
struct Der { Der* func(); };
The vtable for Base gets a corresponding slot:
Base* Base::base_func();
The vtable for Der has two slots
Base* Der::__base_func(){ return __der_func(); }
Der* Der::__der_func(); // This calls the code you write
Anytime func() is called from an object whose static type is Der (or
something derived from Der), the compiler calls der_func. Anytime func() is
called from an object which is not a Der, the compiler calls base_func.
So
Base* b = new Der;
Der* d = new Der;
something = b->func(); // Calls __base_func;
something = d->func(); // Calls __der_func;
Pointer to member works the same way.
&(Base::func);
uses the first vtable entry.
&(Der::func);
normally uses the second vtable entry. Assuming
Base* (Base::*ptr)() = &(Der::func);
is legal code (I'm not sure the standard actually allows this) it would need
to use the first vtable entry.
---
[ 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: Ewert_Ahr._Electronic_GmbH@t-online.de (Gernot)
Date: 1999/05/27 Raw View
Stanley Friesen [Contractor] wrote:
>
>
> The compiler inserts the adjustment "between" the return from the derived
> class function and the assignment to the base class pointer.
>
> One way of doing this is arranging it so that a call to the derived
> function via a base pointer is done thorugh a "stub" function that acts
> somewhat like this:
>
> Derived * Derived::func();
> Base *Base::stub()
> {
> return Derived::func();
> }
>
Ok, that convinces me. Thanks!
But it also shows that the compiler goes to great lengths to simulate
the "good old days" when all overriding virtual functions had to have
the same return types. The necessary additional level of indirection is
also a performance issue. Is this rarely used feature (covariant return
types) really worth the trouble?
---
[ 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: Martin von Loewis <loewis@informatik.hu-berlin.de>
Date: 1999/05/27 Raw View
Ewert_Ahr._Electronic_GmbH@t-online.de (Gernot) writes:
> But it also shows that the compiler goes to great lengths to simulate
> the "good old days" when all overriding virtual functions had to have
> the same return types. The necessary additional level of indirection is
> also a performance issue. Is this rarely used feature (covariant return
> types) really worth the trouble?
The typical application is a copy function
struct Base{
virtual Base* copy(){
return new Base(*this);
}
};
struct Derived:virtual Base{
virtual Derived* copy(){
return new Derived(*this);
}
};
Having copy virtual is required in many cases. However, if you have a
Derived*, copying without covariant return types is inconvenient:
Derived *kopie = dynamic_cast<Derived*>(original->copy());
This is a dynamic_cast, which has a certain runtime cost, in addition
to the textual overhead.
Regards,
Martin
[ 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: "Bill Wade" <bill.wade@stoner.com>
Date: 1999/05/27 Raw View
Gernot wrote in message <374D0E2F.2887@eae.com>...
>Ok, that convinces me. Thanks!
>But it also shows that the compiler goes to great lengths to simulate
>the "good old days" when all overriding virtual functions had to have
>the same return types. The necessary additional level of indirection is
>also a performance issue.
I'd argue that it is rarely a performance issue. It is only an issue when
1) Covariant return types are used.
2) Pointer to base has a different representation than pointer to derived
for
an object. For many implementations this is no problem when Base is a
leftmost non-virtual base of Der.
Also the "extra work" required is
1) An extra vtable entry for Der, (and all classes derived from Der).
2) The body of the extra function (body contains a call to user-written
func() and a derived to base conversion).
2a. This "body" can be in just the class with the covariant return (Der).
2b. This "body" can be in all classes derived from Der which override
func().
2c. As a variation of 2b "body" could inline the user-written func().
3) The work of the extra function call. For 2a this is an extra virtual
function call. For 2b this is an extra non-virtual function call. For 2c
there is no extra function call. In each case this is in a context where a
virtual function call was already occurring.
4) The work of the derived to base conversion could be extra, but it
probably occurred anyway (chances are func() was already working with a Der*
if the author would even consider using covariant return).
>Is this rarely used feature (covariant return types) really worth the
trouble?
For problem domains where you want a clone() function, it makes the
developers' job much simpler (and saves calls to dynamic_cast<> which may be
much more expensive).
By clone() I mean a deep copy, including dynamic type.
struct shape { virtual shape* clone() const; };
shape* p1 = new circle(3); // cloneable objects usually live on the heap.
shape* p2 = p1->clone(); // Another circle(3)
square* p3 = new square(7);
square* p4 = p3->clone();
Without covariants the last line (p4) either
1) Needs a dynamic_cast (significantly expensive).
2) Needs to call a function named clone_square(), a nuisance to remember,
especially if 'square' is spelled 'T' inside a template function.
[ 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: Ewert_Ahr._Electronic_GmbH@t-online.de (Gernot)
Date: 1999/05/25 Raw View
I have a problem with a covariant return type of a virtual function.
Here's the (simplified) class hierarchy:
class Base
{
virtual Base* foo() = 0;
};
class Bar
{
...
};
class Derived : public Base, public Bar
{
virtual Derived* foo();
};
The compiler issues the following error:
Overriding virtual function "foo()" may not return "Derived*" because
class Derived has multiple base classes or a virtual base class.
At first I thought this was a plausible error: In order to interpret a
Derived* as a Base*, the compiler would have to make assumptions about
the resulting memory layout of the two baseclasses within class Derived.
Since this is generally not acceptable, the entire construct is
forbidden.
But then I took a look at what the C++ standard says about covariant
return types:
In section 10.3 it says that if the overriding function returns a
reference or pointer to a derived class, the original return type must
be an unambiguous base class of this type.
Now, isn't "Base" an unambiguous base class of "Derived"?
Where is it stated that multiple inheritance can't be used with
covariant return types?
[ 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: jcoffin@taeus.com (Jerry Coffin)
Date: 1999/05/26 Raw View
In article <374A8277.6F8A@eae.com>, Ewert_Ahr._Electronic_GmbH@t-
online.de says...
[ ... ]
> Where is it stated that multiple inheritance can't be used with
> covariant return types?
As far as I can see, the standard doesn't say such a thing anywhere.
I believe your code is correct, and the compiler you're using is
wrong.
---
[ 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: clamage@eng.sun.com (Steve Clamage)
Date: 1999/05/26 Raw View
Ewert_Ahr._Electronic_GmbH@t-online.de (Gernot) writes:
>I have a problem with a covariant return type of a virtual function.
>Here's the (simplified) class hierarchy:
>class Base
>{
> virtual Base* foo() = 0;
>};
>class Bar
>{
>...
>};
>class Derived : public Base, public Bar
>{
> virtual Derived* foo();
>};
>The compiler issues the following error:
>Overriding virtual function "foo()" may not return "Derived*" because
>class Derived has multiple base classes or a virtual base class.
That is not a restriction in the standard. (Perhaps it is a
documented limitation of the compiler.)
>At first I thought this was a plausible error: In order to interpret a
>Derived* as a Base*, the compiler would have to make assumptions about
>the resulting memory layout of the two baseclasses within class Derived.
>Since this is generally not acceptable, the entire construct is
>forbidden.
The compiler doesn't have to make any assumptions. The compiler
determines the class layouts, and all classes involved must be
complete (definitions seen) at the point of function declaration.
The compiler has everything it needs.
The code you show is legal.
--
Steve Clamage, stephen.clamage@sun.com
---
[ 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: Ewert_Ahr._Electronic_GmbH@t-online.de (Gernot)
Date: 1999/05/26 Raw View
Steve Clamage wrote:
>
> That is not a restriction in the standard. (Perhaps it is a
> documented limitation of the compiler.)
>
> The compiler doesn't have to make any assumptions. The compiler
> determines the class layouts, and all classes involved must be
> complete (definitions seen) at the point of function declaration.
> The compiler has everything it needs.
>
> The code you show is legal.
>
Thanks for your (and Jerry's) reply. Ok, now I know that my compiler
doesn't handle this according to the standard.
But I've gotten curious: How *could* it possibly do this?
For illustration, think of an instance "der" of a class "Derived" with
two base classes "B1" and "B2".
Every working implementation I know of will evaluate the expression
"&der" to either the adress of the "B1" suboject or the "B2" subobject.
Just make the follwing pointer assignments:
Derived* pDer = &der;
B1* pB1 = &der;
B2* pB2 = &der;
Then you will of course be able to access all data members and all
methods of class B1 through the pointer pB1, because the pointer has
been adjusted at compile time.
*But* if you have declared a virtual function to return a "B1*"
and then later override it to return the address of a "Derived"
instance, you have a different situation. This pointer can be and will
be treated as a "B1*" without any adjustments. When the compiler
generates code to use this pointer, it doesn't know (and doesn't need to
know!) anything of classes "B2" and "Derived". Thus accessing members of
class "B1" via this pointer will fail... Or am I missing something?
---
[ 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: stanley@west.sun.com (Stanley Friesen [Contractor])
Date: 1999/05/26 Raw View
In article <374BBBA3.1A62@eae.com>, Gernot <neppert.gernot@eae.com> wrote:
>Every working implementation I know of will evaluate the expression
>"&der" to either the adress of the "B1" suboject or the "B2" subobject.
The virtual mechanism is allowed to "adjust" pointers both during the call,
and now that covariant returns are allowed, during the return. The compiler
inserts the appropriate stuff to make sure the *correct* pointers are passed
in and out. In the case of covariant returns, this usually involves a thunk
or trampoline when calling the overriding function in the derived class via
a base class pointer.
>Just make the follwing pointer assignments:
>
>Derived* pDer = &der;
>B1* pB1 = &der;
>B2* pB2 = &der;
>
>Then you will of course be able to access all data members and all
>methods of class B1 through the pointer pB1, because the pointer has
>been adjusted at compile time.
>*But* if you have declared a virtual function to return a "B1*"
>and then later override it to return the address of a "Derived"
>instance, you have a different situation. This pointer can be and will
>be treated as a "B1*" without any adjustments.
The compiler inserts the adjustment "between" the return from the derived
class function and the assignment to the base class pointer.
One way of doing this is arranging it so that a call to the derived
function via a base pointer is done thorugh a "stub" function that acts
somewhat like this:
Derived * Derived::func();
Base *Base::stub()
{
return Derived::func();
}
> When the compiler
>generates code to use this pointer, it doesn't know (and doesn't need to
>know!) anything of classes "B2" and "Derived".
So it adjusts it prior to finishing the return, when it still knows what
the dynamic type is.
[ 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: Valentin Bonnard <Bonnard.V@wanadoo.fr>
Date: 1999/05/26 Raw View
Gernot wrote:
> I have a problem with a covariant return type of a virtual function.
> Here's the (simplified) class hierarchy:
>
> class Base
> {
> virtual Base* foo() = 0;
> };
>
> class Bar
> {
> ...
> };
>
> class Derived : public Base, public Bar
> {
> virtual Derived* foo();
> };
>
> The compiler issues the following error:
>
> Overriding virtual function "foo()" may not return "Derived*" because
> class Derived has multiple base classes or a virtual base class.
It means that this feature isn't implememted. Instead of generating
incorrect code with no diagnostic (as gcc 2.7.2), or incorrect code
with a diagnostic (gcc ???), or correct code (egcs), it refuses to
compile at all.
But "sorry, not implemented" would make the message much clearer.
You could try to reorder the base classes in Derived definition.
--
Valentin Bonnard
[ 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 ]