Topic: Diatribe about covariant return types
Author: "Bill Wade" <bill.wade@stoner.com>
Date: 1999/09/03 Raw View
Ivan J. Johnson wrote in message <7qim69$458$1@nnrp1.deja.com>...
>
>In article <7qghu6$t0c@library2.airnews.net>,
> "Bill Wade" <bill.wade@stoner.com> wrote:
> [ an example of how to fake covariant return ]
>I like the way that handles virtual inheritance transparently and
>doesn't require any casting. But is it correct that for a hierarchy N
>levels deep it would require a chain of N virtual function calls?
Yes, my solution does require N virtual function calls. By writing more
functions (O(N^2)) you could make the number of function calls const.
>> Andrei's version of Derived::Clone can not point at a MoreDerived object.
>> The version I showed can have d2 point to a (dynamic) MoreDerived object.
>>
>> His version is correct if Derived is not intended for further derivation.
>>
>Sorry, I don't follow you either.
> [ example deleted ]
I meant that Andrei's version was correct if nobody is allowed to write the
MoreDerived class used in my (and your) example.
>Have you ever seen
>cloning used at all, outside of teaching examples?
I believe I've used it with the GOF Prototype pattern (one way to implement
a factory is to provide an initial object that supports clone()) but I'd be
hard-pressed to find the code.
I can find code that uses clone() (but not covariant clone) for a pattern
matching application:
I've got a bunch of data that needs to be "filtered". The filter is virtual
(it depends on the type of data) and in-place (the original values are
replaced with the filtered values). It is done in place because that is
more efficient when a copy is not needed. However some code, which doesn't
know the dynamic type wants to analyze a filtered copy of some const data.
The problem is easily solved with
const data* cd = ...; // Dynamic type might be DensityData;
data* d = cd->clone(); // clone() is const.
d.filter(); // filter is not const.
---
[ 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: "Michael VanLoon" <michaelv@edifecs.com>
Date: 1999/09/02 Raw View
blargg <postmast.root.admi.gov@iname.com> wrote in message
news:user-2808990020160001@aus-as3-010.io.com...
> In article <slrn7s8c69.kc4.sbnaran@localhost.localdomain>,
> sbnaran@uiuc.edu wrote:
>
> > Who uses virtual inheritance anyway?!
>
> Those who understand what it does, I suppose :-)
>
> Some design patterns use it, as does the iostreams package. Neither is
> necessarily a good case for it, though. I personally have used it
maybe
> once total. I don't like it much, since it is like friend in that
multiple
> parties have to agree on its use. But, it is necessary to specify
whether
> a common base class is shared or has separate instances in a diamond
> hierarchy.
I have found it useful when I have a set of base/derived implementation
classes, which also have a base/derived set of interface classes. I end
up with very pure interface classes which each derived interface class
can derived from, but which each implementation class can also derive
from. However, since each derived implementation class also derives
from a base implementation class, the cross-tree develops and virtual
inheritance becomes necessary.
---
[ 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: James Kuyper <kuyper@wizard.net>
Date: 1999/09/02 Raw View
"William H. Bloodworth" wrote:
...
> Wouldn't private inheritance affect polymorphism? I seem to remember
> reading that polymorphism was achieved only through public inheritance.
Polymorphism occurs whenever the inheritance is visible, regardless of
whether or not it's public inheritance. Private inheritance restricts
polymorphic behavior to code which has access to private parts of the
class: i.e. the class members themselves.
---
[ 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: "Ivan J. Johnson" <ivan66@my-deja.com>
Date: 1999/09/02 Raw View
In article <37cdce6b.37191688@news.airmail.net>,
whbloodworth@usa.net wrote:
> On 31 Aug 99 05:40:31 GMT, "Ivan J. Johnson" <ivan66@my-deja.com> wrote:
>
> [snip]
> >The compiler will only accept a static_cast between related types.
> >With a universal base class (UBC), all types are related, so you can
> >cast an Apple to a Mutex and the compiler won't object. That's what I
> >meant by weakened static type checking.
> >
> >A related problem is that with a UBC, any multiple inheritance
> >hierarchy is automatically diamond-shaped.
> >
> >Private inheritance would solve those problems, but you'll need another
> >version of the add_clone template to get it. Or you could avoid the
> >UBC, and instead make Root a template parameter.
>
> Wouldn't private inheritance affect polymorphism? I seem to remember
> reading that polymorphism was achieved only through public inheritance.
>
It would only affect polymorphism to the extent that clients wouldn't
be able to get hold of a Base_* and use it polymorphically (or non-
polymorphically). The idea (which I don't really endorse) was that you
derive from the UBC 'Base_' privately, but subsequent derivations are
still public.
--
Regards,
Ivan J. Johnson
Sacramento, CA
Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
[ 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: "Ivan J. Johnson" <ivan66@my-deja.com>
Date: 1999/09/01 Raw View
In article <7qghu6$t0c@library2.airnews.net>,
"Bill Wade" <bill.wade@stoner.com> wrote:
>
> blargg wrote in message ...
> >In article <7q1h17$t8d@library1.airnews.net>, "Bill Wade"
> ><bill.wade@stoner.com> wrote:
>
> >> Andrei Alexandrescu wrote in message
<7pui9a$65j$1@nnrp1.deja.com>...
>
> >> >class Derived : public Base
> >> >{
> >> > virtual Base* DoClone() { return new Derived; } [ // overrides
> Base::DoClone]
> >> >public:
> >> > Derived* Clone()
> >> > {
> >> > return static_cast<Derived*>(DoClone());
> >> > }
> >> >};
> >> >This idiom effectively emulates covariant return types:
>
> >> You got Derived wrong. To effectively emulate covariant return types
> you'd
> >> write
> >> class Derived: public (possibly virtual) Base
> >> {
> >> virtual Base* DoClone(){ return DoDerivedClone(); }
> >> virtual Derived* DoDerivedClone(){ return new Derived; }
> >> public:
> >> Derived* Clone(){ return DoDerivedClone(); }
> >> };
>
I like the way that handles virtual inheritance transparently and
doesn't require any casting. But is it correct that for a hierarchy N
levels deep it would require a chain of N virtual function calls?
> >I don't follow you - why is this required? The behavior seems identical
> >either way:
>
> The new version of derived is required if you want to do further derivation
> and still call Derived::Clone() and get access to the full object.
> Consider
>
> class MoreDerived: public Derived { ... };
> Derived* d1 = new MoreDerived;
> Derived* d2 = d1->Clone();
>
> Andrei's version of Derived::Clone can not point at a MoreDerived object.
> The version I showed can have d2 point to a (dynamic) MoreDerived object.
>
> His version is correct if Derived is not intended for further derivation.
>
Sorry, I don't follow you either.
#include <iostream>
#include <typeinfo>
using namespace std;
class Base
{
public:
virtual Base* DoClone() { return new Base; }
Base* Clone() { return DoClone(); }
};
class Derived : public Base
{
public:
virtual Base* DoClone() { return new Derived; }
Derived* Clone() { return static_cast<Derived*>(DoClone()); }
};
class MoreDerived : public Derived
{
public:
virtual Base* DoClone() { return new MoreDerived; }
MoreDerived* Clone() { return static_cast<MoreDerived*>(DoClone()); }
};
int main()
{
Derived* d1 = new MoreDerived;
Derived* d2 = d1->Clone();
cout << typeid(*d2).name() << endl;
}
program output: "class MoreDerived"
> >> If you need covariant Clone() in a deep hierarchy it seems that language
> >> support can save a lot of work.
> >
> >If this feature was in fact designed to allow easy cloning, why does it
> >still require one to remember to define the derived clone function
> >(properly, without any compiler warning if done improperly)?
>
> Your template solution is interesting. Most of the time I'd prefer to
> resolve the problem by making a class is either concrete or suitable for
> derivation, but not both.
>
> Was this feature in fact designed to allow easy cloning? It makes cloning
> easier, but it is applicable in other creational patterns:
> Shape* ShapeFactory::make();
> Square* SquareFactory::make();
> and non-creational patterns:
> FoodIterator* Restuarant::BeginMenu();
> FastFoodIterator* BurgerJoint::BeginMenu();
Yes, those are some good uses for covariant return. Have you ever seen
cloning used at all, outside of teaching examples?
>
> The extra layer of indirection required in the last example (returning an
> iterator* rather than an iterator by value) illustrates a major weakness of
> covariant return.
>
--
Regards,
Ivan J. Johnson
Sacramento, CA
Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
[ 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: whbloodworth@usa.net (William H. Bloodworth)
Date: 1999/09/01 Raw View
On 31 Aug 99 05:40:31 GMT, "Ivan J. Johnson" <ivan66@my-deja.com> wrote:
[snip]
>The compiler will only accept a static_cast between related types.
>With a universal base class (UBC), all types are related, so you can
>cast an Apple to a Mutex and the compiler won't object. That's what I
>meant by weakened static type checking.
>
>A related problem is that with a UBC, any multiple inheritance
>hierarchy is automatically diamond-shaped.
>
>Private inheritance would solve those problems, but you'll need another
>version of the add_clone template to get it. Or you could avoid the
>UBC, and instead make Root a template parameter.
Wouldn't private inheritance affect polymorphism? I seem to remember
reading that polymorphism was achieved only through public inheritance.
William Bloodworth
=====================================================================
= Great Achievement REQUIRES Great Effort.
=
= William H. Bloodworth - whbloodworth@usa."net" ICQ: 21901934
=====================================================================
---
[ 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: 1999/08/28 Raw View
>>>>> "SN" == Siemel B Naran <sbnaran@uiuc.edu> writes:
SN> To require programmers to remember to override virtual functions, we have
SN> two main options:
One more comes to mind: based on virtual mixins:
struct Base {
virtual Base* clone() = 0;
};
struct BaseImp: virtual Base {
virtual Base* clone() { return new BaseImp(*this); }
};
struct Derived: virtual Base {
virtual Derived* clone() = 0;
};
struct DerivedImp: virtual Derived {
virtual Derived* clone() { return new DerivedImp(*this); }
};
struct Derived2: virtual Derived {
virtual Derived2* clone() = 0;
};
struct Derived2Imp: virtual Derived2 {
virtual Derived2* clone() { return new Derived2Imp(*this); }
};
Who said "more code"?
With this method, if you didn't suitably implement the "final" clone,
you don't get one, so the problem is solved.
OK: this doesn't work actually if the Imp classes are only intended
(as they should) as mixins. In that case, the type you want to create
is not the mixin type, but the type of the coumpound class, made of
several different mixins, e.g.:
struct Foo: Derived2Imp, Bar {};
What this shows is that C++ inheritance plays two roles here:
specialization and composition, and that we cannot have a pure
composition without an often unwanted speciaziation.
What I intend to propose as a cure is a new use for templates,
allowing the following:
template <typename T, typename U> struct Imp: virtual U {
virtual U* clone() { return new T(*this); }
};
struct Foo: Imp<,Derived2>, Bar {};
Here Imp<,Derived2> would not be inherited, but used as a component
mixin. It would not create a "sub-object" so that the _static_ type of
"this" in the instantiation of Imp<T, U>::clone() would be T instead
of Imp<T, U>.
This kind of use of templates would not create a new type through
instantiation, but "invade" the type of the coumpound class.
With this addition, the commonalities in the above code could be
extracted.
Best Regards!
--------------------------------
P.S. The above case using Bill's and Wil Evers' emulation, which as
already noted and contrarily to Andrei's, works with virtual
inheritance:
struct Base {
Base* clone() { return cloneBase(); }
virtual Base* cloneBase() = 0;
};
struct BaseImp: virtual Base {
virtual Base* cloneBase() { return new BaseImp(*this); }
};
struct Derived: virtual Base {
Derived* clone() { return cloneDerived(); }
virtual Base* cloneBase() { return cloneDerived(); }
virtual Derived* cloneDerived() = 0;
};
struct DerivedImp: virtual Derived {
virtual Derived* cloneDerived() { return new DerivedImp(*this); }
};
struct Derived2: virtual Derived {
Derived2* clone() { return cloneDerived2(); }
virtual Derived* cloneDerived() { return cloneDerived2(); }
virtual Derived2* cloneDerived2() = 0;
};
struct Derived2Imp: virtual Derived2 {
virtual Derived2* cloneDerived2() { return new Derived2Imp(*this); }
};
--
Marc Girod Hiomo 5/1 Voice: +358-9-511 23746
Nokia Telecommunications P.O. Box 320 Mobile: +358-40-569 7954
NWS/NMS/NMS for Data 00045 NOKIA Group Fax: +358-9-511 23580
Finland girod@shire.ntc.nokia.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: Valentin Bonnard <Bonnard.V@wanadoo.fr>
Date: 1999/08/28 Raw View
blargg wrote:
>
> In article <slrn7s8c69.kc4.sbnaran@localhost.localdomain>,
> sbnaran@uiuc.edu wrote:
> > I tried this, and found that the static_cast from A* to B* only works if
> > the inheritance is non-virtual. This code is erroneous:
> >
> > struct A { A * clone() const; virtual A * a_clone() const; };
> > struct B1 : virtual A { B1 * clone() const; virtual A * a_clone() const; };
> > //
> > A * A ::clone() const { return static_cast<A *>(a_clone()); }
> > B1 * B1::clone() const { return static_cast<B1 *>(a_clone()); }
> >
> > What I'd like to know is why C++ decrees that the static_cast is an error.
>
> Because it can't be done statically.
But does ``static'' (resp. ``dynamic'') in static_cast (resp.
dynamic_cast) refers to the implementation or to the way the
insurance that this is really a derived is given, statically
or dynamically ?
BTW, if you know the complete object type, it can be
implemented statically (this is the case with clone).
--
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 ]
Author: sbnaran@uiuc.edu (Siemel B. Naran)
Date: 1999/08/29 Raw View
On 28 Aug 99 12:20:57 GMT, Ivan J. Johnson <ivan66@my-deja.com> wrote:
>In article <slrn7sdjth.foi.sbnaran@localhost.localdomain>,
>> 2. Use the compiler or third party tools to analyze our code and
>> check to see if we've provided the necessary virtual functions.
>
>Do you know of any tools that actually do this?
Not yet...
>template<typename T>
>inline void derived_classes_must_override(T* pThis)
>{
> assert( typeid(*pThis) == typeid(T) );
>}
>
>class Base
>{
>public:
> virtual Base* clone()
> {
> derived_classes_must_override(this);
> return new Base;
> }
>};
IMHO, this idea isn't feasible. When clients write Derived::clone() and
MoreDerived::clone() etc, if they can remember to call
derived_classes_must_override<T>(T*), then surely they can remember to
write the function in the first place :).
>class Derived : public Base
>{
> // oops, forgot to override clone()
>};
--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
[ 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: postmast.root.admi.gov@iname.com (blargg)
Date: 1999/08/30 Raw View
In article <7qa875$78e$1@nnrp1.deja.com>, "Ivan J. Johnson"
<ivan66@my-deja.com> wrote:
> In article <user-2808990045420001@aus-as3-010.io.com>,
> postmast.root.admi.gov@iname.com (blargg) wrote:
> >
> > class Base_ {
> > Base_* clone_() const = 0;
> > };
>
> I assume you mean for clone_() to be virtual?
Yes, thanks for noticing :-)
> > template<class T,class B>
> > class add_clone : public B {
> > Base_* clone_() const {
> > return new T( static_cast<T const&> (*this) );
> > }
> > protected:
> > add_clone() { } // meant to be derived from
> > public:
> > T* clone() const { return static_cast<T*> (clone_()); }
> > };
> >
> > class Base : public add_clone<Base,Base_> {
> > // ...
> > };
> >
> > class Derived : public add_clone<Derived,Base> {
> > // ...
> > };
> >
> > Perhaps this is more convenient (and doesn't rely on covariant return
> > types either).
>
> That's clever, but here are some issues:
>
> 1. You've created a Universal Base Class (Base_), which weakens static
> type-checking.
Where?? Base_ was just meant for the hierarchy that would have been rooted
in Base. It was by *no means* meant as a universal base class. I should
have made that clear. I would never suggest such a thing! :-)
Oh, and even if I were proposing such a universal base class, I fail to
see how it would weaken any type checking. We have void* which acts like a
universal base class pointer already. Type checking isn't reduced at all.
Just like any base class, it's casting back to a derived class that
results in reduced static type checking. Base_ would be specifically *not*
for use by any client code. This could be strengthened by making the
first-level derived classes use private inheritance for Base_.
> 2. How will you handle base class constructors that take arguments?
For the clone case, it is unlikely one would need them , but in general, yes.
> Probably you would need to add some member template constructors to
> add_clone<>.
Yes. That is a problem, and it occurs in many places besides this
(argument forwarding). Since add_clone would be specific to each
hierarchy, the context of it is more limited, making it easier to handle.
> 3. Since the hierarchy is twice as deep as it would be with a more
> conventional approach, RTTI operations will be slower.
Depends on the implementation of RTTI. And I assume you mean
dynamic_cast<>. I have found that dynamic_cast<>, in general, is usually
restricted to specific hierarchies in a system.
I'll add that, on less-than-optimal compilers, this will cause extra
vtable setup to be generated in constructors (even though that particular
value of the vtable pointer would never be used, since the object would
never be that type and accessible for use).
> You'll also need twice as many vtbls.
Also depends on the implementation. On a good compiler that optimizes out
the unnecessary vtable setup, the vtable for the add_clone would never be
referenced, and thus dead-stripped out of the executable.
> 4. How will you handle virtual inheritance?
I won't, since it is such a rare feature to use :)
Seriously, can you provide an example?
[ 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: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 1999/08/30 Raw View
In article <user-3008990817450001@aus-as5-130.io.com>, blargg <postmast.
root.admi.gov@iname.com> writes
>> 4. How will you handle virtual inheritance?
>
>I won't, since it is such a rare feature to use :)
>
>Seriously, can you provide an example?
Well you need virtual inheritance to provide the analogue of interfaces
in C++ (try the results of having multiple instances of an ABC in the
bases of a concrete object - you may find some surprising results when
they are least welcome)
Francis Glassborow Journal Editor, Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA +44(0)1865 246490
All opinions are mine and do not represent those of any organisation
[ 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: "Ivan J. Johnson" <ivan66@my-deja.com>
Date: 1999/08/31 Raw View
In article <user-3008990817450001@aus-as5-130.io.com>,
postmast.root.admi.gov@iname.com (blargg) wrote:
>
> In article <7qa875$78e$1@nnrp1.deja.com>, "Ivan J. Johnson"
> <ivan66@my-deja.com> wrote:
>
> > In article <user-2808990045420001@aus-as3-010.io.com>,
> > postmast.root.admi.gov@iname.com (blargg) wrote:
> > >
> > > class Base_ {
> > > Base_* clone_() const = 0;
> > > };
> >
> > I assume you mean for clone_() to be virtual?
>
> Yes, thanks for noticing :-)
>
> > > template<class T,class B>
> > > class add_clone : public B {
> > > Base_* clone_() const {
> > > return new T( static_cast<T const&> (*this) );
> > > }
> > > protected:
> > > add_clone() { } // meant to be derived from
> > > public:
> > > T* clone() const { return static_cast<T*> (clone_()); }
> > > };
> > >
> > > class Base : public add_clone<Base,Base_> {
> > > // ...
> > > };
> > >
> > > class Derived : public add_clone<Derived,Base> {
> > > // ...
> > > };
> > >
> > > Perhaps this is more convenient (and doesn't rely on covariant
return
> > > types either).
> >
> > That's clever, but here are some issues:
> >
> > 1. You've created a Universal Base Class (Base_), which weakens
static
> > type-checking.
>
> Where?? Base_ was just meant for the hierarchy that would have been
rooted
> in Base. It was by *no means* meant as a universal base class. I
should
> have made that clear. I would never suggest such a thing! :-)
>
> Oh, and even if I were proposing such a universal base class, I fail
to
> see how it would weaken any type checking. We have void* which acts
like a
> universal base class pointer already. Type checking isn't reduced at
all.
> Just like any base class, it's casting back to a derived class that
> results in reduced static type checking. Base_ would be specifically
*not*
> for use by any client code. This could be strengthened by making the
> first-level derived classes use private inheritance for Base_.
The compiler will only accept a static_cast between related types.
With a universal base class (UBC), all types are related, so you can
cast an Apple to a Mutex and the compiler won't object. That's what I
meant by weakened static type checking.
A related problem is that with a UBC, any multiple inheritance
hierarchy is automatically diamond-shaped.
Private inheritance would solve those problems, but you'll need another
version of the add_clone template to get it. Or you could avoid the
UBC, and instead make Root a template parameter.
>
> > 2. How will you handle base class constructors that take arguments?
>
> For the clone case, it is unlikely one would need them , but in
general, yes.
>
> > Probably you would need to add some member template constructors to
> > add_clone<>.
>
> Yes. That is a problem, and it occurs in many places besides this
> (argument forwarding). Since add_clone would be specific to each
> hierarchy, the context of it is more limited, making it easier to
handle.
Still, your member initializer list won't be pretty -- something like:
Derived::Derived(int arg)
: add_clone<Derived, Base, Root>(arg) {}
instead of
Derived::Derived(int arg)
: Base(arg) {}
>
> > 3. Since the hierarchy is twice as deep as it would be with a more
> > conventional approach, RTTI operations will be slower.
>
> Depends on the implementation of RTTI. And I assume you mean
> dynamic_cast<>. I have found that dynamic_cast<>, in general, is
usually
> restricted to specific hierarchies in a system.
>
> I'll add that, on less-than-optimal compilers, this will cause extra
> vtable setup to be generated in constructors (even though that
particular
> value of the vtable pointer would never be used, since the object
would
> never be that type and accessible for use).
>
> > You'll also need twice as many vtbls.
>
> Also depends on the implementation. On a good compiler that optimizes
out
> the unnecessary vtable setup, the vtable for the add_clone would
never be
> referenced, and thus dead-stripped out of the executable.
>
> > 4. How will you handle virtual inheritance?
>
> I won't, since it is such a rare feature to use :)
>
> Seriously, can you provide an example?
>
Not from personal experience, but Stroustrup has the 'Storable' example
in C++PL3rd 15.2.4. And apparently some people do find it useful, or
it wouldn't have made its way into the Standard.
In any case, what is the benefit of using this template that outweighs
the drawbacks? Do you consider it better than alternatives such as
using a macro to generate the boilerplate code in each class, or using
the clone<>() function I described in another posting on this thread?
--
Regards,
Ivan J. Johnson
Sacramento, CA
Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
---
[ 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/08/31 Raw View
blargg wrote in message ...
>In article <7q1h17$t8d@library1.airnews.net>, "Bill Wade"
><bill.wade@stoner.com> wrote:
>> Andrei Alexandrescu wrote in message <7pui9a$65j$1@nnrp1.deja.com>...
>> >class Derived : public Base
>> >{
>> > virtual Base* DoClone() { return new Derived; } [ // overrides
Base::DoClone]
>> >public:
>> > Derived* Clone()
>> > {
>> > return static_cast<Derived*>(DoClone());
>> > }
>> >};
>> >This idiom effectively emulates covariant return types:
>> You got Derived wrong. To effectively emulate covariant return types
you'd
>> write
>> class Derived: public (possibly virtual) Base
>> {
>> virtual Base* DoClone(){ return DoDerivedClone(); }
>> virtual Derived* DoDerivedClone(){ return new Derived; }
>> public:
>> Derived* Clone(){ return DoDerivedClone(); }
>> };
>I don't follow you - why is this required? The behavior seems identical
>either way:
The new version of derived is required if you want to do further derivation
and still call Derived::Clone() and get access to the full object.
Consider
class MoreDerived: public Derived { ... };
Derived* d1 = new MoreDerived;
Derived* d2 = d1->Clone();
Andrei's version of Derived::Clone can not point at a MoreDerived object.
The version I showed can have d2 point to a (dynamic) MoreDerived object.
His version is correct if Derived is not intended for further derivation.
>> If you need covariant Clone() in a deep hierarchy it seems that language
>> support can save a lot of work.
>
>If this feature was in fact designed to allow easy cloning, why does it
>still require one to remember to define the derived clone function
>(properly, without any compiler warning if done improperly)?
Your template solution is interesting. Most of the time I'd prefer to
resolve the problem by making a class is either concrete or suitable for
derivation, but not both.
Was this feature in fact designed to allow easy cloning? It makes cloning
easier, but it is applicable in other creational patterns:
Shape* ShapeFactory::make();
Square* SquareFactory::make();
and non-creational patterns:
FoodIterator* Restuarant::BeginMenu();
FastFoodIterator* BurgerJoint::BeginMenu();
Determining if FastFood IS_A Food is left as an exercise for the student.
The extra layer of indirection required in the last example (returning an
iterator* rather than an iterator by value) illustrates a major weakness of
covariant return.
[ 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: "Ivan J. Johnson" <ivan66@my-deja.com>
Date: 1999/08/30 Raw View
In article <user-2808990045420001@aus-as3-010.io.com>,
postmast.root.admi.gov@iname.com (blargg) wrote:
>
> class Base_ {
> Base_* clone_() const = 0;
> };
I assume you mean for clone_() to be virtual?
>
> template<class T,class B>
> class add_clone : public B {
> Base_* clone_() const {
> return new T( static_cast<T const&> (*this) );
> }
> protected:
> add_clone() { } // meant to be derived from
> public:
> T* clone() const { return static_cast<T*> (clone_()); }
> };
>
> class Base : public add_clone<Base,Base_> {
> // ...
> };
>
> class Derived : public add_clone<Derived,Base> {
> // ...
> };
>
> Perhaps this is more convenient (and doesn't rely on covariant return
> types either).
That's clever, but here are some issues:
1. You've created a Universal Base Class (Base_), which weakens static
type-checking.
2. How will you handle base class constructors that take arguments?
Probably you would need to add some member template constructors to
add_clone<>.
3. Since the hierarchy is twice as deep as it would be with a more
conventional approach, RTTI operations will be slower. You'll also
need twice as many vtbls.
4. How will you handle virtual inheritance?
--
Regards,
Ivan J. Johnson
Sacramento, CA
Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
---
[ 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: "Ivan J. Johnson" <ivan66@my-deja.com>
Date: 1999/08/30 Raw View
In article <slrn7sgc15.2dq.sbnaran@localhost.localdomain>,
sbnaran@uiuc.edu wrote:
>
> On 28 Aug 99 12:20:57 GMT, Ivan J. Johnson <ivan66@my-deja.com> wrote:
>
> >template<typename T>
> >inline void derived_classes_must_override(T* pThis)
> >{
> > assert( typeid(*pThis) == typeid(T) );
> >}
> >
> >class Base
> >{
> >public:
> > virtual Base* clone()
> > {
> > derived_classes_must_override(this);
> > return new Base;
> > }
> >};
>
> IMHO, this idea isn't feasible. When clients write Derived::clone()
and
> MoreDerived::clone() etc, if they can remember to call
> derived_classes_must_override<T>(T*), then surely they can remember to
> write the function in the first place :).
>
> >class Derived : public Base
> >{
> > // oops, forgot to override clone()
> >};
>
While it's true that you can devise a combination of errors that defeat
the check, I think my template makes the "failure to override" error a
lot less likely. The client would first need to remember to override
clone() in Derived, while forgetting to include a call to
derived_classes_must_override() in the override. I think this error is
very unlikely, because the code would typically either be cut and
pasted from Base, or generated by macro. But, having made this
unlikely error, the client would then need to compound it by deriving a
MoreDerived class and forgetting to override clone() there.
BTW, have you thought of any cases besides cloning where a way to force
virtual override would be useful? One that occurred to me was in
optimizing virtual calls. Suppose you have one virtual function that
calls another within the same class:
class Base {
virtual void foo() { }
virtual void bar() { foo(); }
}
You could optimize the call by using a qualified name:
virtual void bar() { Base::foo(); }
but then if someone derives from Base and overrides foo() without
overriding bar(), they won't get the result they expect.
--
Regards,
Ivan J. Johnson
Sacramento, CA
Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
---
[ 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: sbnaran@uiuc.edu (Siemel B. Naran)
Date: 1999/08/27 Raw View
On 26 Aug 99 21:41:26 GMT, Andrei Alexandrescu <andrewalex@hotmail.com> wrote:
> // forgot to override bubble return type BubbleClone function
To require programmers to remember to override virtual functions, we have
two main options:
1. Introduce the "%=0" feature into the language. So
virtual Base * clone() const % = 0;
means that Base::clone() is not pure virtual, but it is inherited as
pure virtual.
2. Use the compiler or third party tools to analyze our code and check to
see if we've provided the necessary virtual functions.
--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
[ 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: sbnaran@uiuc.edu (Siemel B. Naran)
Date: 1999/08/27 Raw View
On 26 Aug 99 21:38:39 GMT, Bill Wade <bill.wade@stoner.com> wrote:
>Andrei Alexandrescu wrote in message <7pui9a$65j$1@nnrp1.deja.com>...
It took me a while to figure out the differences between Bill's and
Andrei's code, so here goes...
>>class Base
>>{
>> virtual Base* DoClone() { return new Base; }
>>public:
>> Base* Clone() { return DoClone(); }
>>};
>>
>>class Derived : public Base
>>{
>> virtual Base* DoClone() { return new Derived; }
>>public:
>> Derived* Clone()
>> {
>> return static_cast<Derived*>(DoClone());
>> }
>>};
Suppose we also have a class More just like class Derived.
In the call base->Clone(), where typeid(base)==typeid(More),
1. call to Base::Clone() to return a Base*
2. call to More::DoClone() to return a Base*
3. call to "new More" which returns a More*
4. implicit cast of More* to Base* to return a Base* (finish step 2)
5. return Base* (finish step 1)
In summary, we have the following calls:
a. new More
b. More* to Base*
>You got Derived wrong. To effectively emulate covariant return types you'd
>write
>
>class Derived: public (possibly virtual) Base
>{
> virtual Base* DoClone(){ return DoDerivedClone(); }
> virtual Derived* DoDerivedClone(){ return new Derived; }
>public:
> Derived* Clone(){ return DoDerivedClone(); }
>};
>
>class More: public Derived
>{
> // Don't need DoClone(), inherited version works fine, but is less optimal
> virtual Derived* DoDerivedClone(){ return MoreClone(); }
> virtual MoreClone(){ return new More; }
>public:
> More* Clone(){ return MoreClone(); }
>};
Error: missing return type on More::MoreClone(). Asumme More*.
In the call base->Clone(), where typeid(base)==typeid(More),
1. call to Base::Clone() to return a Base*
2. call to Derived::DoClone() to return a Base*
3. call to More::DoDerivedClone() to return a Derived*
4. call to More::MoreClone() to return a More*
5. call to "new More" which returns a More*
6. return More* (finish step 4)
7. implicit cast of More* to Derived* to return a Derived* (finish step 3)
8. implicit cast of Derived* to Base* to return a Base* (finish step 2)
9. return Base* (finish step 1)
In summary, we have the following calls:
a. new More
b. More* to Derived*
c. Derived* to Base*
First, as far as pointer types go, both Andrei's and Bill's methods are
equivalent. Both do "new More" and cast the More* to a Base*. However,
Bill's method uses more function calls and uses an intermediate type
-- here a Derived* -- so it is slower. Bill's method also results in a
larger virtual table because there are more virtual functions.
Second, even if the language supported covariant return types for
value types, I would propose that there always be a direct conversion
from the C to A. So if we have A::action() that returns an a, and
B::action() that returns a b, and C::action() that returns a c, then
the compiler generated thunks for C::action() should be
a C::action() { return c(); }
b C::action() { return static_cast<b>(c()); }
c C::action() { return static_cast<a>(c()); }
If we convert c to b to a, then we have the following possibility:
there is no direct conversion from c to a, but there are conversions
from c to b and b to a. The problem with this is that it breaks
parallel structure: there is a direct conversion of C to A, but there
is no direct conversion of c to a. A more practical objection to
this approach is that it is slower.
--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
[ 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: "Ivan J. Johnson" <ivan66@my-deja.com>
Date: 1999/08/28 Raw View
In article <7q1h17$t8d@library1.airnews.net>,
"Bill Wade" <bill.wade@stoner.com> wrote:
> [...] To effectively emulate covariant return types you'd
> write
>
> class Derived: public (possibly virtual) Base
> {
> virtual Base* DoClone(){ return DoDerivedClone(); }
> virtual Derived* DoDerivedClone(){ return new Derived; }
> public:
> Derived* Clone(){ return DoDerivedClone(); }
> };
>
> class More: public Derived
> {
> // Don't need DoClone(), inherited version works fine, but is less
optimal
> virtual Derived* DoDerivedClone(){ return MoreClone(); }
> virtual MoreClone(){ return new More; }
> public:
> More* Clone(){ return MoreClone(); }
> };
>
> So every time you want to change the return type and support future
changes
> you've got to write three functions, rather than just one. If you
mess up
> (perhaps writing More::DoClone() rather than More::DoDerivedClone() or
> putting 'new' in Derived::DoClone or Derived::Clone) the compiler
won't tell
> you.
>
> If you need covariant Clone() in a deep hierarchy it seems that
language
> support can save a lot of work.
Another way to get the effect, with a lot less typing, is with function
templates:
#include <assert.h>
#include <typeinfo>
template<typename DerivedT, typename BaseT>
inline DerivedT* verify_clone(const DerivedT& d, BaseT* pClone)
{
assert ( typeid(*pClone) == typeid(d) );
// use dynamic_cast here if you want to handle virtual bases
return static_cast<DerivedT*>(pClone);
}
// provides covariant return
template<typename T>
inline T* clone(const T& t)
{
return verify_clone(t, t.do_clone());
}
class Base
{
public:
virtual Base* do_clone() const { return new Base(*this); }
};
class Derived : public Base
{
public:
virtual Base* do_clone() const { return new Derived(*this); }
};
void f()
{
Base b;
Derived d;
Base* pb = clone(b);
Derived* pd = clone(d);
}
At runtime, this should detect all "failure to override" errors,
including the ones that Andrei was concerned about (which the compiler
can't detect in any case). Variations are possible, such as having the
'clone' function return a smart_ptr<T> to get "covariant smart pointer
return types."
Since I have yet to put this code into a real project, I would
appreciate comments on whether this is a good approach.
--
Regards,
Ivan J. Johnson
Sacramento, CA
Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
---
[ 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: 1999/08/28 Raw View
>>>>> "Wil" == Wil Evers <bouncer@dev.null> writes:
>> - it cannot be emulated.
Wil> But it can.
You are right (same remark also by Bill Wade).
Thanks.
--
Marc Girod Hiomo 5/1 Voice: +358-9-511 23746
Nokia Telecommunications P.O. Box 320 Mobile: +358-40-569 7954
NWS/NMS/NMS for Data 00045 NOKIA Group Fax: +358-9-511 23580
Finland girod@shire.ntc.nokia.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: postmast.root.admi.gov@iname.com (blargg)
Date: 1999/08/28 Raw View
In article <slrn7s8c69.kc4.sbnaran@localhost.localdomain>,
sbnaran@uiuc.edu wrote:
> On 25 Aug 1999 15:54:47 GMT, Marc Girod <girod@stybba.ntc.nokia.com> wrote:
>
> >- With virtual inheritance, the cast from Base to Derived is not
> > static, but dynamic.
>
> I tried this, and found that the static_cast from A* to B* only works if
> the inheritance is non-virtual. This code is erroneous:
>
> struct A { A * clone() const; virtual A * a_clone() const; };
> struct B1 : virtual A { B1 * clone() const; virtual A * a_clone() const; };
> //
> A * A ::clone() const { return static_cast<A *>(a_clone()); }
> B1 * B1::clone() const { return static_cast<B1 *>(a_clone()); }
>
> What I'd like to know is why C++ decrees that the static_cast is an error.
Because it can't be done statically.
struct VB { };
struct X : virtual VB {
int i;
};
struct Y : virtual VB {
int j;
};
struct Z : virtual VB {
int k;
};
struct A : X, Y, Z { };
Consider the object structure. If we had a single X/Y/Z object, the VB
portion would be at some fixed offset from a pointer to the X/Y/Z object.
Now, create an A object. Convert it to a VB&. Now, how can code, which
only knows about Y, convert this VB& to a Y&? Try the same for X and Z.
There is no fixed offset that can be applied.
Virtual inheritance is generally handled by having a pointer/offset in
each direct derived class, which can be used to find the virtual base
sub-object from a pointer to the derived class. The virtual base can't
have any back pointer, partly because any class can be a virtual base (for
example, the empty struct VB above), and partly because the number of back
pointers would be unbounded.
[snip]
> >class A {...};
> >void foo(A&);
> >class B: virtual public A {...};
> >void bar(B&);
> >class C: virtual public B {...};
> >etc...
>
> Who uses virtual inheritance anyway?!
Those who understand what it does, I suppose :-)
Some design patterns use it, as does the iostreams package. Neither is
necessarily a good case for it, though. I personally have used it maybe
once total. I don't like it much, since it is like friend in that multiple
parties have to agree on its use. But, it is necessary to specify whether
a common base class is shared or has separate instances in a diamond
hierarchy.
---
[ 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: postmast.root.admi.gov@iname.com (blargg)
Date: 1999/08/28 Raw View
In article <7q1h17$t8d@library1.airnews.net>, "Bill Wade"
<bill.wade@stoner.com> wrote:
> Andrei Alexandrescu wrote in message <7pui9a$65j$1@nnrp1.deja.com>...
> >Consider:
> >
> >class Base
> >{
> > virtual Base* DoClone() { return new Base; }
> >public:
> > Base* Clone() { return DoClone(); }
> >};
> >
> >class Derived : public Base
> >{
> > virtual Base* DoClone() { return new Derived; }
> >public:
> > Derived* Clone()
> > {
> > return static_cast<Derived*>(DoClone());
> > }
> >};
> >
> >This idiom effectively emulates covariant return types:
>
> You got Derived wrong. To effectively emulate covariant return types you'd
> write
>
> class Derived: public (possibly virtual) Base
> {
> virtual Base* DoClone(){ return DoDerivedClone(); }
> virtual Derived* DoDerivedClone(){ return new Derived; }
> public:
> Derived* Clone(){ return DoDerivedClone(); }
> };
I don't follow you - why is this required? The behavior seems identical
either way:
void f( Derived* d ) {
Derived* nd = d->Clone();
}
(let's forget the fact that Clone should be named NewSameType, or its
implementation fixed)
[snip]
> If you need covariant Clone() in a deep hierarchy it seems that language
> support can save a lot of work.
If this feature was in fact designed to allow easy cloning, why does it
still require one to remember to define the derived clone function
(properly, without any compiler warning if done improperly)?
class Base_ {
Base_* clone_() const = 0;
};
template<class T,class B>
class add_clone : public B {
Base_* clone_() const {
return new T( static_cast<T const&> (*this) );
}
protected:
add_clone() { } // meant to be derived from
public:
T* clone() const { return static_cast<T*> (clone_()); }
};
class Base : public add_clone<Base,Base_> {
// ...
};
class Derived : public add_clone<Derived,Base> {
// ...
};
Perhaps this is more convenient (and doesn't rely on covariant return
types either).
---
[ 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: postmast.root.admi.gov@iname.com (blargg)
Date: 1999/08/28 Raw View
In article <7q1sc7$jee$1@nnrp1.deja.com>, Andrei Alexandrescu
<andrewalex@hotmail.com> wrote:
> In article <user-2508990154050001@aus-as3-137.io.com>,
> postmast.root.admi.gov@iname.com (blargg) wrote:
> [snip]
>
> Hi,
>
> I start to feel like I'm making a fool out of myself. I guess I should
> have applied Mark Twain's saying: "It is better to keep your mouth
> closed and let people think you are a fool than to open it and remove
> all doubt". But...
Nahhh.
> Seems like much of your discussion stems from the follwing example.
Only because much of yours seemed to revolve around it :-)
> > class Derived2 : public Derived {
> > virtual Base* DoClone() { return new Derived2; }
> > public:
> > // forgot public Clone function
> > };
> >
> > Error not caught.
>
> How come? What's the error here? I don't see any. If I forgot to
> implement public Clone, I won't be able to get a Derived2*, I'll get
> only a Derived. This is a compile time error.
OK. You're correct:
class Derived {
// ...
public:
Derived* Clone() { return DoClone(); }
};
Since the DoClone() call is virtual, it will properly clone the object.
Any other affects of this "error" will most-likely be caught at compile
time, though some could slip through (the return type of Clone() will be
Derived*, and not Derived2*, so functions which overload on the pointer
type would be able to tell that Derived2 forgot to implement Clone(), but
they wouldn't necessarily get an error).
> The problem is these bubble return types don't help the compiler
> catching a much more pernicious error:
[error of forgetting to define clone function for a particular derived class]
Yes, agreed for this issue with cloning. Back to covariant return types...
[snip]
> If bubble return types don't help me in making this idiom safe (which
> they were intended to help), what can I say?
Is that what covariant return types were made for?
> You point me to the patterns that are directly helped by this feature. I
> don't know of any. Could you be more specific?
I remember reading some on Robert C. Martin's web site (there are several
articles that are freely available in the PDF format). They are at the
following URL:
http://www.oma.com/Publications/publications.html
One that uses covariant return types
>> Design Patterns for Dealing with Dual Inheritance Hierarchies in C++
>> Robert C. Martin
>> C++ Report, April, 1997
The URL for it is:
http://www.oma.com/PDF/dih.pdf
I searched the others too, but they didn't use covariant return types.
That's interesting.. huh
I feel myself starting to see things your way :-) I'll have to consider
seriously whether I think it's a generally-useful feature.
---
[ 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: "Ivan J. Johnson" <ivan66@my-deja.com>
Date: 1999/08/28 Raw View
In article <slrn7sdjth.foi.sbnaran@localhost.localdomain>,
sbnaran@uiuc.edu wrote:
> To require programmers to remember to override virtual functions, we
have
> two main options:
>
> 1. Introduce the "%=0" feature into the language. So
> virtual Base * clone() const % = 0;
> means that Base::clone() is not pure virtual, but it is inherited
as
> pure virtual.
That would be nice, because the error would be caught at compile time.
But I'm not sure how useful it would really be. The need to force
virtual overrides seems to be fairly unusual for non-abstract classes,
and when it does occur, it can be done at runtime without a language
change.
>
> 2. Use the compiler or third party tools to analyze our code and
check to
> see if we've provided the necessary virtual functions.
Do you know of any tools that actually do this?
Here is another way (which you might have meant to suggest in 2. above):
#include <assert.h>
#include <typeinfo>
// verifies that the pointer's dynamic type
// is the same as its static type
template<typename T>
inline void derived_classes_must_override(T* pThis)
{
assert( typeid(*pThis) == typeid(T) );
}
class Base
{
public:
virtual Base* clone()
{
derived_classes_must_override(this);
return new Base;
}
};
class Derived : public Base
{
// oops, forgot to override clone()
};
int main()
{
Base b;
Derived d;
b.clone(); // ok
d.clone(); // raises assertion
return 0;
}
Also, as I suggest in another post in this thread (which seems to be
lost in cyberspace at the moment), if you use a non-member function
template for cloning, you can put the run-time check there instead of
pasting it into every class.
--
Regards,
Ivan J. Johnson
Sacramento, CA
Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
---
[ 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: sbnaran@uiuc.edu (Siemel B. Naran)
Date: 1999/08/25 Raw View
On 24 Aug 99 21:03:47 GMT, Andrei Alexandrescu <andrewalex@hotmail.com> wrote:
>class Base
>{
> virtual Base* DoClone() { return new Base; }
>public:
> Base* Clone() { return DoClone(); }
>};
>
>class Derived : public Base
>{
> virtual Base* DoClone() { return new Derived; }
>public:
> Derived* Clone()
> {
> return static_cast<Derived*>(DoClone());
> }
>};
This seems to be one case where overriding a non-virtual function seems
entirely reasonable. When clients call base->clone(), the program still
calls the derived version of the actual function -- ie. Base::doclone()
or Derived::doclone() or MoreDerived::doclone() -- but then casts the
result to whatever is expected, in this case a Base*. Similar remarks
hold if we call derived->clone().
BTW, the clone() functions should be const.
>2. Covariant return types are not trivial to implement.
>
>They require thunks to be used in the vtable, which is admittedly not
>rocket science but not trivial, especially given that you don't get
>almost anything in exchange.
The method in part 1 uses only one line of virtual functions -- namely
Base * class::doclone() const. But the virtual thunk method of part 2
creates many lines of virtual functions. This results in larger virtual
tables. If the implementation is unable to garauntee one copy of the
virtual table, this overhead will be more significant.
For example:
struct A { virtual A f(); virtual A g(); };
struct B : A { virtual B f(); virtual B g(); };
struct C : B { virtual C f(); virtual C g(); };
The virtual table of class C has not 2 entries, but 6 entries!
--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
[ 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: 1999/08/25 Raw View
>>>>> "AA" == Andrei Alexandrescu <andrewalex@hotmail.com> writes:
AA> I made the statement that the "covariant return types" feature is
AA> worthless and I'd like to back this up.
I thought you had already received valid replies to your earlier
posting:
http://www.deja.com/=dnc/getdoc.xp?AN=508963985
The case for their use for me is the following:
- With virtual inheritance, the cast from Base to Derived is not
static, but dynamic.
- Being dynamic, it can fail (not guaranteed by the compiler).
I.e. you are trading a compile-time check for a run-time one, with the
usual problems:
- you require code to check results, which will not be exercised until
something gets broken later during maintenance
- when these errors will be made in maintenance, they will not be
detected until run-time
Now the use is cloning of polymorphic objects, especially with several
levels of specialization. You want to be able to write client code
using one level, yet be allowed to further specialize.
I don't know what problems you may be referring to.
class A {...};
void foo(A&);
class B: virtual public A {...};
void bar(B&);
class C: virtual public B {...};
etc...
Wrap up:
- it is useful for simple, frequent uses;
- it cannot be emulated.
Best Regards!
Marc
--
Marc Girod Hiomo 5/1 Voice: +358-9-511 23746
Nokia Telecommunications P.O. Box 320 Mobile: +358-40-569 7954
NWS/NMS/NMS for Data 00045 NOKIA Group Fax: +358-9-511 23580
Finland girod@shire.ntc.nokia.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: postmast.root.admi.gov@iname.com (blargg)
Date: 1999/08/25 Raw View
In article <7pui9a$65j$1@nnrp1.deja.com>, Andrei Alexandrescu
<andrewalex@hotmail.com> wrote:
> I made the statement that the "covariant return types" feature is
> worthless and I'd like to back this up.
>
> 1. Covariant return types are easy to simulate. They're not really
> needed.
I don't think anyone will argue with this. I don't think they were added
because anyone though they allowed (at an implementation level)
fundamentally new things to be done.
> Consider:
>
> class Base
> {
> virtual Base* DoClone() { return new Base; }
> public:
> Base* Clone() { return DoClone(); }
> };
>
> class Derived : public Base
> {
> virtual Base* DoClone() { return new Derived; }
> public:
> Derived* Clone()
> {
> return static_cast<Derived*>(DoClone());
> }
> };
First, your definitions of "clone" are interesting. They sound more like
"create new object of same type".
class Derived2 : public Derived {
virtual Base* DoClone() { return new Derived2; }
public:
// forgot public Clone function
};
Error not caught.
> This idiom effectively emulates covariant return types:
>
> Base* pb;
> Derived* pd;
> ...
> Base* pb2 = pb->Clone();
> Derived* pd2 = pd->Clone();
>
> To claim that this requires more discipline than covariant return types
> is false,
This is simply wrong. It does require more discipline, as shown above.
> as their usage is fragile at best - in the cloning scenario,
> if you forget to implement DoClone() for a derived class, you're doomed
> and the compiler cannot do anything for you. With or without covariant
> return types.
In the cloning scenario, yes. Covariant return types have more uses than
just cloning. This sounds more like a case for a safer clone idiom, and
not against covariant return types.
> 2. Covariant return types are not trivial to implement.
>
> They require thunks to be used in the vtable, which is admittedly not
> rocket science but not trivial, especially given that you don't get
> almost anything in exchange.
Why couldn't a compiler implement them just as you've shown above? That
wouldn't require any changes to the virtual dispatch mechanism.
Essentially for a covariant return type, the type could always treated
internally as the base virtual function return type. When the actual type
is known statically to be different, it is cast back to that type. i.e.
this
struct Base {
virtual Base* f();
};
struct Derived : Base {
virtual Derived* f();
};
void f( Derived* d ) {
Derived* d2 = d->f();
}
is treated as this internally
struct Base {
virtual Base* f();
};
struct Derived : Base {
virtual Base* f();
// ^^^^^
// return type is treated as Base* (but in func definition
// the return statement must return a Derived*, of course)
};
void f( Derived* d ) {
Derived* d2 = static_cast<Derived*> (d->f());
// ^^^^^^^^^^^^^^^^^^^^^
// note this implicit (guaranteed-safe) cast
}
> 3. Covariant return types are conceptually weak.
>
> Here's where I'll get flamed. IMHO, covariant return types fool you into
> thinking that you get the best of two worlds: static checking and
> polymorphism. You don't.
But you get exactly these.
> What you get is obviating of the need of a
> one-line function or a cast.
Either way, it's polymorphism and static type checking.
> And this only in rare situations (when
> you're lucky enough to know the static type of the object you deal
> with.)
There are some patterns that use covariant return types in a significant
way. When using these, it's not just "luck" that you know more about the
type of the object, it's design.
> Knowing the static type and working polymorphically (with
> virtual functions) don't mix.
Sure they do.
> Was this worth a language feature?
This is the big question - what tradeoffs were involved, what reason was
it added?
> 4. Covariant return types don't support any healthy, safe idioms.
The example you showed at the beginning is contrary to this.
> They were introduced mainly for cloning polymorphic objects. As anyone
> who actually tried this knows, they are simply useless in assisting you
> to achieve that.
Useless? Now that's nice.
> I have yet to see code that's even remotely better, safer, or more
> elegant due to the usage of this feature. I have serious doubts I ever
> will.
Take a look at some design patterns that use it.
> In a nutshell, covariant return types are utterly good for nothing. Many
> much, much more interesting proposals of additions to C++ were rejected
> on fewer grounds than the ones enumerated above. That's what's been
> driving me nuts enough to post this :o).
So basically you don't think the feature was worth its benefit. This is a
lot different than it being "simply useless".
> I think there's a similarity between covariant return types and bubble
> sort: they're not any good at all, but they have a cute name with which
> you can stun your secretary before asking her out. ("Sorry for being a
> bit nervous today, but a covariant return type went out of control. We
> had to apply a bubble sort to it. Listen, what if we had diner tonight
> and relax with some sushi and champagne?")
LOL.
---
[ 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: "Gene Bushuyev" <gbush@my-deja.com>
Date: 1999/08/25 Raw View
Andrei Alexandrescu <andrewalex@hotmail.com> wrote in message
news:7pui9a$65j$1@nnrp1.deja.com...
> I made the statement that the "covariant return types" feature is
> worthless and I'd like to back this up.
>
> 1. Covariant return types are easy to simulate. They're not really
> needed.
>
Well, you can simulate it of course, but you'll have to repeat the same code
in each derived class and you need even more virtual functions if you're
using multiple inheritance. So I think it makes good sense to make a
provision in language for covariant return types to reduce purely mechanical
work from the part of programmers.
Gene Bushuyev.
---
[ 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: 1999/08/26 Raw View
>>>>> "MG" == Marc Girod <girod@stybba.ntc.nokia.com> writes:
MG> - With virtual inheritance, the cast from Base to Derived is not
MG> static, but dynamic.
One minor detail I must add for completion: neither of the two
compilers I commonly use supports covariant returns in presence of
virtual inheritance :-(
Nevertheless: I lack them.
Best Regards!
Marc
--
Marc Girod Hiomo 5/1 Voice: +358-9-511 23746
Nokia Telecommunications P.O. Box 320 Mobile: +358-40-569 7954
NWS/NMS/NMS for Data 00045 NOKIA Group Fax: +358-9-511 23580
Finland girod@shire.ntc.nokia.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: "Bill Wade" <bill.wade@stoner.com>
Date: 1999/08/26 Raw View
Andrei Alexandrescu wrote in message <7pui9a$65j$1@nnrp1.deja.com>...
>Consider:
>
>class Base
>{
> virtual Base* DoClone() { return new Base; }
>public:
> Base* Clone() { return DoClone(); }
>};
>
>class Derived : public Base
>{
> virtual Base* DoClone() { return new Derived; }
>public:
> Derived* Clone()
> {
> return static_cast<Derived*>(DoClone());
> }
>};
>
>This idiom effectively emulates covariant return types:
You got Derived wrong. To effectively emulate covariant return types you'd
write
class Derived: public (possibly virtual) Base
{
virtual Base* DoClone(){ return DoDerivedClone(); }
virtual Derived* DoDerivedClone(){ return new Derived; }
public:
Derived* Clone(){ return DoDerivedClone(); }
};
class More: public Derived
{
// Don't need DoClone(), inherited version works fine, but is less optimal
virtual Derived* DoDerivedClone(){ return MoreClone(); }
virtual MoreClone(){ return new More; }
public:
More* Clone(){ return MoreClone(); }
};
So every time you want to change the return type and support future changes
you've got to write three functions, rather than just one. If you mess up
(perhaps writing More::DoClone() rather than More::DoDerivedClone() or
putting 'new' in Derived::DoClone or Derived::Clone) the compiler won't tell
you.
If you need covariant Clone() in a deep hierarchy it seems that language
support can save a lot of work.
---
[ 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: Andrei Alexandrescu <andrewalex@hotmail.com>
Date: 1999/08/26 Raw View
In article <0cebd2601081989CPIMSSMTPE01@msn.com>,
"Gene Bushuyev" <gbush@my-deja.com> wrote:
> Well, you can simulate it of course, but you'll have to repeat the
same code
> in each derived class and you need even more virtual functions if
you're
> using multiple inheritance. So I think it makes good sense to make a
> provision in language for covariant return types to reduce purely
mechanical
> work from the part of programmers.
It's almost the same work with bubble... sorry, covariant return types.
You have to add a function instead of two (or use a cast). No
extra safety at all. I don't see having to do only a little bit less
work to be of much advantage.
Andrei
Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
---
[ 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: Andrei Alexandrescu <andrewalex@hotmail.com>
Date: 1999/08/26 Raw View
In article <user-2508990154050001@aus-as3-137.io.com>,
postmast.root.admi.gov@iname.com (blargg) wrote:
[snip]
Hi,
I start to feel like I'm making a fool out of myself. I guess I should
have applied Mark Twain's saying: "It is better to keep your mouth
closed and let people think you are a fool than to open it and remove
all doubt". But...
Seems like much of your discussion stems from the follwing example.
> class Derived2 : public Derived {
> virtual Base* DoClone() { return new Derived2; }
> public:
> // forgot public Clone function
> };
>
> Error not caught.
How come? What's the error here? I don't see any. If I forgot to
implement public Clone, I won't be able to get a Derived2*, I'll get
only a Derived. This is a compile time error.
The problem is these bubble return types don't help the compiler
catching a much more pernicious error:
class Base
{
public:
virtual Base* BubbleClone() = 0;
};
class Derived
{
public:
virtual Derived* BubbleClone() { return new Derived; }
};
class Derived2 : public Derived
{
// forgot to override bubble return type BubbleClone function
};
Now if you call BubbleClone for a pointer to Base which actually points
to a Derived2, you get a pointer to a Derived. This is LETHAL.
If bubble return types don't help me in making this idiom safe (which
they were intended to help), what can I say?
You point me to the patterns that are directly helped by this feature. I
don't know of any. Could you be more specific?
I agree with a few points in your message, but as much of it is based
upon the code example that you post (and which I don't think it's
problematic), I'd love to keep the problem open and get more input from
you. Thanks.
Andrei
Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
---
[ 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: sbnaran@uiuc.edu (Siemel B. Naran)
Date: 1999/08/26 Raw View
On 25 Aug 1999 15:54:47 GMT, Marc Girod <girod@stybba.ntc.nokia.com> wrote:
>- With virtual inheritance, the cast from Base to Derived is not
> static, but dynamic.
I tried this, and found that the static_cast from A* to B* only works if
the inheritance is non-virtual. This code is erroneous:
struct A { A * clone() const; virtual A * a_clone() const; };
struct B1 : virtual A { B1 * clone() const; virtual A * a_clone() const; };
//
A * A ::clone() const { return static_cast<A *>(a_clone()); }
B1 * B1::clone() const { return static_cast<B1 *>(a_clone()); }
What I'd like to know is why C++ decrees that the static_cast is an error.
>- Being dynamic, it can fail (not guaranteed by the compiler).
Not true: static_cast can also fail -- and the failure will be harder to
detect. The static_cast just means that the compiler can work out the
offset bytes at compile time. So the static_cast from A* to B* is
garaunteed to compile (if the inheritance is non-virtual). But if the
object is not really a B*, the cast is incorrect and the entire program
is undefined. With dynamic_cast, the compiler works out the offset
bytes at run time. And if the cast fails, at least you get some
indication of the failure, in this case a null pointer.
>class A {...};
>void foo(A&);
>class B: virtual public A {...};
>void bar(B&);
>class C: virtual public B {...};
>etc...
Who uses virtual inheritance anyway?!
--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
---
[ 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: sbnaran@uiuc.edu (Siemel B. Naran)
Date: 1999/08/26 Raw View
On 25 Aug 99 19:23:17 GMT, blargg <postmast.root.admi.gov@iname.com> wrote:
>There are some patterns that use covariant return types in a significant
>way. When using these, it's not just "luck" that you know more about the
>type of the object, it's design.
Example please.
--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------
---
[ 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: "Wil Evers" <bouncer@dev.null>
Date: 1999/08/26 Raw View
Andrei Alexandrescu <andrewalex@hotmail.com> wrote..
> class Base
> {
> virtual Base* DoClone() { return new Base; }
> public:
> Base* Clone() { return DoClone(); }
> };
>
> class Derived : public Base
> {
> virtual Base* DoClone() { return new Derived; }
> public:
> Derived* Clone()
> {
> return static_cast<Derived*>(DoClone());
> }
> };
...to which, in article <1yiu6352pb.fsf@sirion.ntc.nokia.com>, Marc Girod
<girod@stybba.ntc.nokia.com> replied
>
> - With virtual inheritance, the cast from Base to Derived is not
> static, but dynamic.
> - Being dynamic, it can fail (not guaranteed by the compiler).
[snip]
> Wrap up:
> - it is useful for simple, frequent uses;
> - it cannot be emulated.
But it can.
class Base {
/* ... */
virtual Base *cloneBase() const
{ return new Base(*this); }
public :
Base *clone() const
{ return cloneBase(); }
};
class Derived : public Base {
/* ... */
/* final */ virtual Base *cloneBase() const
{ return cloneDerived(); }
virtual Derived *cloneDerived() const
{ return new Derived(*this); }
public :
Derived *clone() const
{ return cloneDerived(); }
};
Note that from the caller's perspective, the hierarchy's interface is the
same as the one we'd have if we'd used covariant return types.
Furthermore, this technique does not use any casts, and it can be applied
to hierarchies with multiple inheritance and virtual base classes.
The only benefit of covariant return types is that it saves us from having
to write stuff like this.
- Wil
Wil Evers, DOOSYS IT Consultants, Maarssen, Holland
[Wil underscore Evers at doosys dot 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: Andrei Alexandrescu <andrewalex@hotmail.com>
Date: 1999/08/24 Raw View
I made the statement that the "covariant return types" feature is
worthless and I'd like to back this up.
1. Covariant return types are easy to simulate. They're not really
needed.
Consider:
class Base
{
virtual Base* DoClone() { return new Base; }
public:
Base* Clone() { return DoClone(); }
};
class Derived : public Base
{
virtual Base* DoClone() { return new Derived; }
public:
Derived* Clone()
{
return static_cast<Derived*>(DoClone());
}
};
This idiom effectively emulates covariant return types:
Base* pb;
Derived* pd;
...
Base* pb2 = pb->Clone();
Derived* pd2 = pd->Clone();
To claim that this requires more discipline than covariant return types
is false, as their usage is fragile at best - in the cloning scenario,
if you forget to implement DoClone() for a derived class, you're doomed
and the compiler cannot do anything for you. With or without covariant
return types.
2. Covariant return types are not trivial to implement.
They require thunks to be used in the vtable, which is admittedly not
rocket science but not trivial, especially given that you don't get
almost anything in exchange.
3. Covariant return types are conceptually weak.
Here's where I'll get flamed. IMHO, covariant return types fool you into
thinking that you get the best of two worlds: static checking and
polymorphism. You don't. What you get is obviating of the need of a
one-line function or a cast. And this only in rare situations (when
you're lucky enough to know the static type of the object you deal
with.) Knowing the static type and working polymorphically (with
virtual functions) don't mix. Was this worth a language feature?
4. Covariant return types don't support any healthy, safe idioms.
They were introduced mainly for cloning polymorphic objects. As anyone
who actually tried this knows, they are simply useless in assisting you
to achieve that.
I have yet to see code that's even remotely better, safer, or more
elegant due to the usage of this feature. I have serious doubts I ever
will.
In a nutshell, covariant return types are utterly good for nothing. Many
much, much more interesting proposals of additions to C++ were rejected
on fewer grounds than the ones enumerated above. That's what's been
driving me nuts enough to post this :o).
I think there's a similarity between covariant return types and bubble
sort: they're not any good at all, but they have a cute name with which
you can stun your secretary before asking her out. ("Sorry for being a
bit nervous today, but a covariant return type went out of control. We
had to apply a bubble sort to it. Listen, what if we had diner tonight
and relax with some sushi and champagne?")
I'd be happy to hear opinions about this. (Not about the dating tip.)
Andrei
Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
---
[ 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 ]