Topic: Single inheritance and casts


Author: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/22
Raw View
In article <35DCE82B.71FEA82D@ix.netcom.com>, "Paul D. DeRocco"
<pderocco@ix.netcom.com> wrote:

> I pointed out
>that for this to work, all these otherwise unrelated types would have to
>have _something_ physical in common, namely a standardized location for
>the vtbl pointer. Then, dynamic_cast could be made to work from a poly*
>to any polymorphic type.

>However, dynamic_cast, as currently defined,
>can also operate on non-polymorphic types, as long as it isn't expected
>to do anything more than static_cast.

  This seems to be a change that I did not know of: ARM 1994 says that
dynamic_cast only operates on pointers to a polymorhic type.

>No, I'm talking about something that's absolutely portable. If you
>create an empty base class called "poly" with a virtual destructor, and
>use it as a virtual base class for all your classes, then there will be
>one "poly" handle by which each object can be manipulated. In this case,
>dynamic_cast should correctly be able to cast from a poly* to any
>derived type, no matter how deeply buried in the inheritance tree it may
>be, as long as the cast isn't ambiguous.

  The reason I bring this discussion up is that this idea does not work in
the context I describe: I have repetedly said that one starts with such
handles, but when one wants to optimize, one moves on to what I describe.

  One wants objects to self-mutate in place of a fixed memory allocation
without the reference that other objects have to this mutated object is
being destroyed: It is not possible, under these circumstances, to say
first mutate the object and then recompute the references that the other
object have to this object by a new dynamic_cast.

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


[ 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: "Paul D. DeRocco" <pderocco@ix.netcom.com>
Date: 1998/08/17
Raw View
Hans Aberg wrote:
>
>   I will explain why I think that the suggestion by Siemel Naran
> <sbnaran@ceg.uiuc.edu> does not work. It has to do with the offsets
> computed when converted to a base class (i.e. the fact that pointers
> to a polymorphic object need not point at the full object).
>
>   This will also explain why I think C++ needs to be extended in this
> respect, making the feature possible (perhaps using a "pointer to the
> full object of a polymorphic type", as I suggested in another post.

Forgive me if you already understand the following, but I'll say it
because I suspect you might not.

Given a void*, or even some as-yet-invented poly*, there is no way the
compiler, or run-time code generated by the compiler, can find out
_anything_ about the type of the object being pointed to. In order to do
this for polymorphic objects, the compiler must be told what the
specific base class of the polymorphic object is. This is because RTTI
needs a hidden pointer within the object (typically the vtbl pointer),
and this pointer may be at different offsets (or may even not exist at
all) for different base classes. Once this offset has been nailed down
for a particular base class, it remains unchanged in the derived
classes, which is what allows RTTI (and virtual functions) to work
within the inheritance tree defined by a particular base class.

--

Ciao,
Paul


[ 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: David R Tribble <david.tribble@noSPAM.central.beasys.com>
Date: 1998/08/17
Raw View
Siemel Naran wrote:
> ...
> The only way to support dynamic inheritance is, I think, through union
> inheritance.  This way, we can get the sizeof's right.  So when we say
> "class D : public union { D1,D2 } D" or something like that, the
> compiler knows: (1) that we intend to mutate between D1 and D2, and
> (2) that sizeof(D)=max(sizeof(D1),sizeof(D2)).

Unions of non-primitive types is a bad idea.  Consider the problem,
for instance, of which destructor to call when the union (or the
object containing it) goes out of scope.  Unless the language
somehow allowed you to associate a "tag" member with the union to
indicate which object is "active" within the union at any given
moment, this could be disasterous (or at least ambiguous).

The general principle in C++ thus has been that (with the exception
of a few primitive and POD types within unions) a chunk of memory
has one, and only one, type at any given moment.  The exception for
POD and primitive types causes no harm because those types don't
have constructors and destructors.

Coming back to the original problem, the proper way to convert a
D1 object into a D2 object is to define a type conversion function
that does just that.  You could probably get away with using a
placement new operator ('D2::operator new(D2 *obj)') to convert the
object in place.  Just be careful that members of D2 don't overlap
member of D1 in unpredictable ways; this could be very tricky if
you don't know the exact layouts of both classes.  Oh, and don't
forget to ensure that sizeof(D1)==sizeof(D2) or you'll be in
for some surprises when 'delete' is called on the object.

-- David R. Tribble, dtribble@technologist.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: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/17
Raw View
In article <35D759AC.DCB5B430@ix.netcom.com>, "Paul D. DeRocco"
<pderocco@ix.netcom.com> wrote:
>Given a void*, or even some as-yet-invented poly*, there is no way the
>compiler, or run-time code generated by the compiler, can find out
>_anything_ about the type of the object being pointed to. In order to do
>this for polymorphic objects, the compiler must be told what the
>specific base class of the polymorphic object is.

  If one has the poly* pointer, then one knows that it is the pointer to
the full object of a polymorhic type, so it must be possible to reach at
least one vtbl of that object (say, if one decides it to be the first
word). Then just make sure to put the information needed to dynamic_cast
to other objects on that vtbl.

>This is because RTTI
>needs a hidden pointer within the object (typically the vtbl pointer),
>and this pointer may be at different offsets (or may even not exist at
>all) for different base classes.

  A polymorphic type in C++ must have at least one virtual function, so
one needs a vtbl in such a case.

  Otherwise, I just tried to find a description of the problem needed to
be solved: If others have good solutions, let's hear.

  One alternative solution to poly* would be to say that in the case of
single inheritance one can ensure (by means of a new C++ construct) that
the base class is always put first and the pointer to the object is always
the same as the pointer to the full object. (I think this is what most,
but not all compilers, do. Those that don't would be forced to support it
as a feature used when called for by the programmer.)

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


[ 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@fermi.ceg.uiuc.edu (Siemel Naran)
Date: 1998/08/17
Raw View
On 17 Aug 1998 20:00:57 GMT, David R Tribble

>Unions of non-primitive types is a bad idea.  Consider the problem,
>for instance, of which destructor to call when the union (or the
>object containing it) goes out of scope.  Unless the language
>somehow allowed you to associate a "tag" member with the union to
>indicate which object is "active" within the union at any given
>moment, this could be disasterous (or at least ambiguous).

Right.  Unions of non-primitive types is a bad idea.  Which constructor
do we call, which destructor do we call?

This is what I intended:  Like dynamic_cast, union of non-primitive
types can only be applied to classes that have a virtual pointer.
After all, we have a virtual pointer sitting there inside the class,
so why not use it as an identification tag?  Put it to use!

The syntax for class unions would be
     // D1,D2 are class names; d is a variable name
     union {
          D1;
          D2;
          () : D1(...) { }
     } d;

Here's an important restriction: class D1 and class D2 must have exactly
the same types in their virtual tables, and in the same order.  This is
so that a virtual pointer to class D1 can be safely substituted with a
virtual pointer to class D2.

The point is to change the inheritance dynamically at run time.
Normally, the virtual pointer has type "vtable *const", so it can't be
changed.  With unions, the vtable would have type "vtable *".  See
below.


>Coming back to the original problem, the proper way to convert a
>D1 object into a D2 object is to define a type conversion function
>that does just that.  You could probably get away with using a
>placement new operator ('D2::operator new(D2 *obj)') to convert the
>object in place.  Just be careful that members of D2 don't overlap
>member of D1 in unpredictable ways; this could be very tricky if
>you don't know the exact layouts of both classes.  Oh, and don't
>forget to ensure that sizeof(D1)==sizeof(D2) or you'll be in
>for some surprises when 'delete' is called on the object.

Yes.  This is the approach I currently use.  But it does entail
two dereferences at times.

Suppose you have a class Base with virtual functions.
We also have class Derived1 and class Derived2.
And suppose "d" has type Derived1.  It's virtual pointer points to an
object of ultimate derived type Derived1.

Now we want to change its Derived part in place from Derived1 to
Derived2 but leave the Base part intact.  With unions, we just change
the Derived part, and the virtual pointer for "d" will be updated
automatically.

class Base;
class Derived : public Base { union { Derived1, Derived2 } derived; ... };

BTW, the compiler will know how much space to allocate for "derived".
The answer is max(sizeof(Derived1),sizeof(Derived2)).

Right now, if we want to mutate the Derived part of an object, we
either create a whole new object (Derived2 and Base part), or we
do something like

class Base { };
class DerivedN { };
class Derived1 : public DerivedN { };
class Derived2 : public DerivedN { };
class Derived : public Base { DerivedN* d; ... };

One problem is that if we call a virtual function through a pointer to
a Base, there will be two vptr de-references: one to get from
Base to Derived, one to get from DerivedN to Derived1/Derived2.  Plus,
there's no compiler check that the interfaces of Derived, DerivedN,
Derived1, Derived2 match.



But things can get tricky:

class Final : public Derived { ... };


--
----------------------------------
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: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/18
Raw View
In article <35D886CB.38B6@noSPAM.central.beasys.com>,
dtribble@technologist.com wrote:
>Coming back to the original problem, the proper way to convert a
>D1 object into a D2 object is to define a type conversion function
>that does just that.  You could probably get away with using a
>placement new operator ('D2::operator new(D2 *obj)') to convert the
>object in place.  Just be careful that members of D2 don't overlap
>member of D1 in unpredictable ways; this could be very tricky if
>you don't know the exact layouts of both classes.  Oh, and don't
>forget to ensure that sizeof(D1)==sizeof(D2) or you'll be in
>for some surprises when 'delete' is called on the object.

  You have probably not understood the problem: In order to gain some
speed, one wants to pack different elements into the same memory
allocation, thereby avoiding tracing an extra pointer. This is done by
changing "operator new", so it produces the same pointer to the full
object when the mutation D1 -> D2 takes place, and so it does not make any
difference the relation between sizeof(D1) and sizeof(D2) as long as the
memory allocation is not exceeded.

  But then, the D1 and D2 objects have a different offsets relative to
their common base class B, the latter which is used to identify these
objects: Several other objects have a B* pointer used to identify the
memory location and what is in it. But if D1 and D2 have different offsets
relative to B, these B* pointers outdate in the mutation D1 -> D2.

  So we are discussing means to make this process work: One way would be
the knowledge that the B* pointer is the same as the pointer to the full
object void*, other solutions might be something like poly*. Fancy
solutions are out of the questions, because it is going to be too slow.

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


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






Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1998/08/18
Raw View
Hans Aberg wrote:
>
>   I will explain why I think that the suggestion by Siemel Naran
> <sbnaran@ceg.uiuc.edu> does not work. It has to do with the offsets
> computed when converted to a base class (i.e. the fact that pointers to a
> polymorphic object need not point at the full object).
>
>   This will also explain why I think C++ needs to be extended in this
> respect, making the feature possible (perhaps using a "pointer to the full
> object of a polymorphic type", as I suggested in another post.
>
>   So, there is a base class B, and two derived classes D1 : B, and D2 : B.
> All these objects will get the same memory allocations, and part of the
> problem is mutate a D1 object into a D2 object. But is that was all, the
> problem would have been easy to solve:
>
>   The objects also have pointers of type B* to them, and to make things
> worse, there are several such independent pointers to the object. So when
> a D1 object is mutated into a D2 object, it is not sufficient to just
> write the D2 object into the space that D1 already has, it must be fit
> into the place in such a way that the old B* pointers now point at D2.
>
>   Say we have pointers
>     B* bp;
>     D1* d1p;
>     D2* d2p;
>     void* v1p = d1p;
>     void* v2p = d2p;
> were it is required that bp = d1p and bp = d2p. Then the picture is this:
>
>                    v2p --> ----
>  - - - - v1p -> ----  - - |    | - - - - - - -
>                |    |     |    |<-- d2p    |
>   B* bp ------>| D1 |---->| D2 |           | Memory allocation that
>         d1p -->|    |     |    |           | the D1 object gets.
>                 ----       ----            |
>                                            v
>  - - - - - - - - - - - - - - - - - - - - - - -
>
>   So the problem is that if we fit the D2 object into the space that the
> D1 object has already gotten in such a way that their void* pointers v1p
> and v2p agree, then the dynamic_cast<D1*>(d1p) and dynamic_cast<D2*>(d2p)
> will compute to different values, which is not allowed in the application
> intended.
>
>   Then my idea of C++ extension is this: Instead of using the polymorphic
> pointer B* bp, introduce a new polymorphic variation of void*, which I
> call "poly*", which has the same value as the void* but with the
> additional polymorphic information so that it works with dynamic_cast.

So if I understand you correctly, the following should help:

template<class With> void Replace(B* p)
{
  // Get the address the new object must have...
  void* with_address = static_cast<With*>(p);

  // ...destruct the old object...
  p->~B();

  // ...and construct the new object in-place
  new(with_address) With;

  // Now the new object should have it's B part at the same address
  // as the old one; that is, p is still a valid pointer to it
}


However, I don't think it's good design. Instead, you should make an
indirection, like the following:

class BProxy: public B
{
  B* theRealB;
public:
// forward all operations to theRealB

// and the "mutator":
  template<class Der> void mutate()
  {
    newB = new Der;
    delete theRealB;
    theRealB = newB;
  }
};

Now, all your B pointers really point to BProxy objects, which
just forward the calls to the real object. (If you insist using
a buffer, just embed that buffer in your BProxy object; since
the only pointer to the real object - theRealB - is under your
control, you don't have to play dirty casting tricks. In
addition, it will still work if your objects get too big for the
buffer, since in that case, you can fall back to dynamic
allocation transparently.)


[ 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: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/18
Raw View
In article <35D987FA.E12EF826@physik.tu-muenchen.de>, Christopher Eltschka
<celtschk@physik.tu-muenchen.de> wrote:
>However, I don't think it's good design. Instead, you should make an
>indirection, like the following:

  One wants to use the method I described when indirection is considered
too slow: So one starts with indirection, and then proceeds to what I
describe when optimizations are becoming interesting.

>So if I understand you correctly, the following should help:
>
>template<class With> void Replace(B* p)
>{
>  // Get the address the new object must have...
>  void* with_address = static_cast<With*>(p);
>
>  // ...destruct the old object...
>  p->~B();
>
>  // ...and construct the new object in-place
>  new(with_address) With;
>
>  // Now the new object should have it's B part at the same address
>  // as the old one; that is, p is still a valid pointer to it
>}

  The problem is that if one writes in D2 at the same place as D1 so that
their B* pointers agree, then the void* pointer of D2 may have a negative
offset relative the void* pointer if D1, and so if one does not keep track
of that, one may overwrite the end of another object allocated below D1.

  If one knows that the compiler allocates the B part first, then all
these pointers agree so there is no problem, but the C++ standard does not
guarantee this.

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


[ 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: "Paul D. DeRocco" <pderocco@ix.netcom.com>
Date: 1998/08/19
Raw View
Hans Aberg wrote:
>
> If one has the poly* pointer, then one knows that it is the pointer to
> the full object of a polymorhic type, so it must be possible to reach
> at least one vtbl of that object (say, if one decides it to be the
> first word). Then just make sure to put the information needed to
> dynamic_cast to other objects on that vtbl.

That would require that the vtbl always be in the first word, for any
polymorphic object, but it would also require that it be impossible to
convert a pointer to any non-polymorphic type to a poly*, which would be
kind of a departure for C++.

You can accomplish this sort of thing anyway, if you're writing all the
classes yourself, just by making sure they all have some single base
class, called "poly". In fact, if you make it a virtual base class with
a virtual destructor, you can use it even in a multiple inheritance
situation.

--

Ciao,
Paul


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






Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1998/08/19
Raw View
One thing at the beginning: It's not a good idea to change the order
of quoted parts - the reader might get a wrong impression of what I
wrote.

Hans Aberg wrote:

> In article <35D987FA.E12EF826@physik.tu-muenchen.de>, Christopher Eltschka
> <celtschk@physik.tu-muenchen.de> wrote:
> >However, I don't think it's good design. Instead, you should make an
> >indirection, like the following:

>   One wants to use the method I described when indirection is considered
> too slow: So one starts with indirection, and then proceeds to what I
> describe when optimizations are becoming interesting.


This was not following, but preceding the previous quotation:

> >So if I understand you correctly, the following should help:

> >template<class With> void Replace(B* p)
> >{
> >  // Get the address the new object must have...
> >  void* with_address = static_cast<With*>(p);

> >  // ...destruct the old object...
> >  p->~B();

> >  // ...and construct the new object in-place
> >  new(with_address) With;

> >  // Now the new object should have it's B part at the same address
> >  // as the old one; that is, p is still a valid pointer to it
> >}

>   The problem is that if one writes in D2 at the same place as D1 so that
> their B* pointers agree, then the void* pointer of D2 may have a negative
> offset relative the void* pointer if D1, and so if one does not keep track
> of that, one may overwrite the end of another object allocated below D1.

You have to check that your object fits into the slot anyway. In this
case, you don't just have to check that you don't overwrite the next
buffer, but also that you don't overwrite the previous buffer.
That is, you need to have enough room at the left as well as at
the right side. I don't see a fundamental difference to the
case where you have only the right bound to consider - it's just
two conditions of the same style instead of one.


>   If one knows that the compiler allocates the B part first, then all
> these pointers agree so there is no problem, but the C++ standard does not
> guarantee this.

There's still the problem of the size of D1 and D2. In the
general case, you have a "left size" and a "right size".
So, there's no additional problem - just duplication of something
you must care of anyway.



[ 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@fermi.ceg.uiuc.edu (Siemel Naran)
Date: 1998/08/19
Raw View

In comp.std.c++, on 18 Aug 1998 18:18:34 GMT, Hans Aberg wrote:
>In article <35D987FA.E12EF826@physik.tu-muenchen.de>, Christopher Eltschka

>>However, I don't think it's good design. Instead, you should make an
>>indirection, like the following:

Two problems here.  First, note that all of this is part of an
inheritance hierarchy.

class Base; // just a base class, ok
class Derived1; // not derived from Base, but overrides funcs in Base!
class Derived2; // ditto!

class DerivedN; // also a base class!
// has the same interface as Derived1 and Derived2

class Derived; // derived class, ok
// derived from Base, contains pointer to DerivedN
// has same interface at DerivedN

Suppose you call a virtual function f() from a pointer to the Base
class.  We have to dereference a vptr once to realize that we must
call not Base::f() but rather DerivedN::f().  Then we have to
dereference a vptr here to realize that we must call not
DerivedN::f() but rather Derived1::f() or Derived2::f().  This is
time consuming: there are two vptr de-references.  Ideally, there
should be only one dereference.

Another problem is that we have to manually ensure that the
interfaces of DerivedN, Derived1, Derived2, and Derived are
the same -- the types of the virtual functions, their order, and
their accessibility have to all be the same.  We should only have
to write the interface once.  Let the compiler do the dirty work.

-----

>  The problem is that if one writes in D2 at the same place as D1 so that
>their B* pointers agree, then the void* pointer of D2 may have a negative
>offset relative the void* pointer if D1, and so if one does not keep track
>of that, one may overwrite the end of another object allocated below D1.

>  If one knows that the compiler allocates the B part first, then all
>these pointers agree so there is no problem, but the C++ standard does not
>guarantee this.

Fine, I suppose the standard could allow this -- that is, allow us
to change the most derived part of an object in place while letting
the pointers to the base parts remain valid.  But for that matter,
the standard should allow us to change any part of an object.
Maybe we want to change the most base part of an object.  Or maybe
in the inheritance Base ==> Derived ==> MostDerived, we want to
change the Base part in place, or change the Derived part in place.
Your proposed change would only allow us to change MostDerived in
place.

And then, there's the problem of how much space to allocate for
the MostDerived part.  If MostDerived can be MostDerived1 or
MostDerived2, then clearly the amount of space we need is
max(sizeof(MostDerived1),sizeof(MostDerived2)).


-----

Here's what I've come up with.  Consider the
Base ==> Derived1/Derived2 hierarchy given above.

class Base // just some base class
{
     public:
          virtual ~Base();
          Base();
          virtual void f(int) = 0;
          virtual void g(int) = 0;
          // user defined copy ctor, operator= ok
};

struct Part1 { virtual ~Part1(); char hello[4]; };
struct Part2 { virtual ~Part2(); char world[8]; };

class Derived : public Base : union<Part1,Part2>
// destructors Part1::~Part1() and Part2::~Part2() must be virtual because
// the vptr in the class serves as a tag, telling us which part is active
// the vptr of class Part1/Part2 will be merged with the vptr of class Derived!
{
     public:
          enum Case { ONE=1, TWO };

     private:
          int alpha; // ok, but can't access this variable from Part1 or Part2

     public:
          // one copy of each of these functions
          virtual ~Derived(); // destructor must be virtual
          // BTW, no compiler generated default ctor ever
          Derived(Case); // demonstrates conditional initialization
          Derived(const Derived&); // can be compiler generated
          Derived& operator=(const Derived&); // ditto
          void mutate(const Part1&); // mutate into a Part1 object
          void mutate(const Part2&); // mutate into a Part2 object

          // two copies of each of these functions
          // one for Part1, one for Part2
          union<Part1,Part2>
          {
               virtual void f(int); // Part1::f(int), Part2::f(int) -- see below
               virtual void g(int) = 0; // no need to define
               virtual void h(int) = 0; // new virtual function, ok
               inline void i(int); // warning: inline not allowed!
          };
};

/*
   sizeof(Derived) =   sizeof(Base)
                     + sizeof(alpha)
                     + sizeof(max(sizeof(Part1),sizeof(Part2)))
                     + sizeof(one virtual pointer)
*/



Derived::~Derived()
{
}
// now calls either Part1::~Part1() or Part::~Part2(), whichever is active
// to determine which part is active, look at vptr
// now alpha destroyed with ~alpha()
// now Base destroyed with ~Base()

Derived::Derived(Derived::Case c)
     : Base(), alpha(0) // direct initialization
     : union<Part1,Part2>(c==ONE ? Part1():Part2())
          // this is 'conditional initialization'
          // conditional expression results in temp
          // this temp copied into Derived using Part1/Part2 copy ctor
          // so this is copy initialization (unless copy optimized away)
{
}
// now, the vptr of the object can be set to either the vtable of
// Derived1 (rather Derived::Part1) or to the vtable of Derived2

// note that construction and destruction follow reverse orders

Derived::Derived(const Derived& that)
     : Base(that), alpha(that.alpha)
     : union<Part1,Part2>(that)
          // copies that.Part1 or that.Part2, whichever is active
          // BTW, the vptr of the object that indicates which part is active
{
}

// another way to write copy ctor -- using conditional initialization
// a little ugly: allows us to copy Part2 part even if Part1 part is active
Derived::Derived(const Derived& that)
     : Base(that), alpha(that.alpha)
     : union<Part1,Part2>(dynamic_cast<Derived::Part1*>(&that) ?
                             Part1(that) : Part2(that))
        // the dynamic_cast is used as an upcast
{
}

Derived& Derived::operator=(const Derived& that)
{
     if (this==&that) return *this;
     Base::operator=(that);
     union<Part1,Part2>::operator=(that);
        // if that and this have Part1 active, just Part1::operator=
        /* if that has Part1 active and this has Part2 active,
                call ~Part2(), then Part1(), then Part1::operator=
                change the virtual pointer automatically!
         */
     return *this;
}

void Derived::mutate(const Part1& p)
{
     union<Part1,Part2>::operator=(p);
        // as above, may call ~Part2() followed by Part1(), change vptr
        // then calls Part1::operator=
}


void Derived::Part1::i(int) { return 1; };
void Derived::Part2::i(int) { return 2; };

void Derived::Part1::f(int) { return i(hello[0]); }
void Derived::Part2::f(int) { return i(world[0]); }

// compiler generated
// void Derived::Part1::g(int i) { return Base::g(i); }
// void Derived::Part2::g(int i) { return Base::g(i); }



--
----------------------------------
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: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/20
Raw View
In article <slrn6tm96b.gs6.sbnaran@fermi.ceg.uiuc.edu>,
sbnaran@KILL.uiuc.edu wrote:
>>  If one knows that the compiler allocates the B part first, then all
>>these pointers agree so there is no problem, but the C++ standard does not
>>guarantee this.
>
>Fine, I suppose the standard could allow this -- that is, allow us
>to change the most derived part of an object in place while letting
>the pointers to the base parts remain valid.

  I think of the question I brought up a specialty feature, something that
is needed in order to speed up the code by avoiding an extra pointer
indirection under very special circumstances. The idea is that this should
be possible to do conveniently in C++ under those special circumstances.

>  But for that matter,
>the standard should allow us to change any part of an object.

  So generalizations are great, but at least I would think twice before
moving ahead to such generalizations: Can such generalizations be
implemented efficiently by the compiler, without the loss of speed?

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


[ 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: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/20
Raw View
In article <35DA4D73.1B0747B9@ix.netcom.com>, "Paul D. DeRocco"
<pderocco@ix.netcom.com> wrote:
>That would require that the vtbl always be in the first word, for any
>polymorphic object, but it would also require that it be impossible to
>convert a pointer to any non-polymorphic type to a poly*, which would be
>kind of a departure for C++.

  I am not sure I understand this: In C++ it is not now allowed to
dynamic_cast say a void* to a polymorhic pointer, so why should one be
allowed to dynamic_cast a void* to a poly*?

>You can accomplish this sort of thing anyway, if you're writing all the
>classes yourself, just by making sure they all have some single base
>class, called "poly". In fact, if you make it a virtual base class with
>a virtual destructor, you can use it even in a multiple inheritance
>situation.

  No, the feature can be provided for some compilers in some cases
provided one knows how the compiler implements the classes: For example,
in the case of single inheritance, in case the base class is put first by
the compiler.

  But this is not anything that the C++ standard guarantees.

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


[ 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: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/20
Raw View
In article <35DA8BE7.14409DCF@physik.tu-muenchen.de>, Christopher Eltschka
<celtschk@physik.tu-muenchen.de> wrote:
>You have to check that your object fits into the slot anyway. In this
>case, you don't just have to check that you don't overwrite the next
>buffer, but also that you don't overwrite the previous buffer.
>That is, you need to have enough room at the left as well as at
>the right side. I don't see a fundamental difference to the
>case where you have only the right bound to consider - it's just
>two conditions of the same style instead of one.

  The reason is that one provides the pointer with "operator new": It is
easy for"operator new" to check that the memory allocation is not
exceeded, but it has no way of checking for negative offsets (allocations
below the void* pointer it provides).

>There's still the problem of the size of D1 and D2. In the
>general case, you have a "left size" and a "right size".
>So, there's no additional problem - just duplication of something
>you must care of anyway.

  Why not write a working version, and then report back?

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


[ 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@fermi.ceg.uiuc.edu (Siemel Naran)
Date: 1998/08/20
Raw View
On 20 Aug 1998 15:59:36 GMT, Hans Aberg <haberg@REMOVE.matematik.su.se> wrote:
>In article <slrn6tm96b.gs6.sbnaran@fermi.ceg.uiuc.edu>,

>>Fine, I suppose the standard could allow this -- that is, allow us
>>to change the most derived part of an object in place while letting
>>the pointers to the base parts remain valid.

>  I think of the question I brought up a specialty feature, something that
>is needed in order to speed up the code by avoiding an extra pointer
>indirection under very special circumstances. The idea is that this should
>be possible to do conveniently in C++ under those special circumstances.

The two issues are intertwined.  You want to be able to change the
vptr at run time -- dynamic inheritance.  You also want to be able
to have enough space for the new object.



>>  But for that matter,
>>the standard should allow us to change any part of an object.

>  So generalizations are great, but at least I would think twice before
>moving ahead to such generalizations: Can such generalizations be
>implemented efficiently by the compiler, without the loss of speed?

This would be a drastic error!  If we made the 'simple' idea work,
then what if in the next version we want to make the 'complex' idea
work.  Since the working version was tailored specifically for the
'simple' idea, we probably won't be able to extend it to the complex
idea.

Here's a more pragmatic objection to your proposed change.  Consider
this function that takes an object 'd' and mutates it in place to
something else depending on the second argument.

class Base;
class Derived; // derived from Base

// mutate "d" in place depending on value of "alpha"
// most derived part of "d" is mutated -- ie, Base part is not touched
void mutate(Derived& d, int alpha);

Now try this:

Derived d;
mutate(d,3); // fine


No problem.  Now try this:

class MoreDerived; // derived from Derived
MoreDerived m;
mutate(m,3); // we should expect this function to work!

Now the mutate function is mutating not the most derived part of
the object, but a middle part.  There might not be enough
space.  But is this what we expect?  No, we expect the mutate
function to mutate the Derived part and leave the Base and
MoreDerived part untouched.  We demand from this function that
it never fail.


--
----------------------------------
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: "Paul D. DeRocco" <pderocco@ix.netcom.com>
Date: 1998/08/21
Raw View
Hans Aberg wrote:
>
> In article <35DA4D73.1B0747B9@ix.netcom.com>, "Paul D. DeRocco"
> <pderocco@ix.netcom.com> wrote:
> >That would require that the vtbl always be in the first word, for any
> >polymorphic object, but it would also require that it be impossible
> >to convert a pointer to any non-polymorphic type to a poly*, which
> >would be kind of a departure for C++.
>
>   I am not sure I understand this: In C++ it is not now allowed to
> dynamic_cast say a void* to a polymorhic pointer, so why should one be
> allowed to dynamic_cast a void* to a poly*?

I think we're getting confused here. You suggested that there be some
pseudo-type called "poly" that can be used as a stand-in for any types
of polymorphic object, even different unrelated types. I pointed out
that for this to work, all these otherwise unrelated types would have to
have _something_ physical in common, namely a standardized location for
the vtbl pointer. Then, dynamic_cast could be made to work from a poly*
to any polymorphic type. However, dynamic_cast, as currently defined,
can also operate on non-polymorphic types, as long as it isn't expected
to do anything more than static_cast. I was merely mentioning that it
wouldn't be possible to convert from such a non-polymorphic pointer to a
poly*, because when dynamic_cast tried to do the reverse, it would be
mis-interpreting some data in that object as a vtbl pointer. Thus, to
avoid this, converting from a non-polymorphic pointer to a poly* would
have to be prohibited in the first place. Minor point.

> >You can accomplish this sort of thing anyway, if you're writing all
> >the classes yourself, just by making sure they all have some single
> >base class, called "poly". In fact, if you make it a virtual base
> >class with a virtual destructor, you can use it even in a multiple
> >inheritance situation.
>
>   No, the feature can be provided for some compilers in some cases
> provided one knows how the compiler implements the classes: For
> example, in the case of single inheritance, in case the base class is
> put first by the compiler.
>
>   But this is not anything that the C++ standard guarantees.

No, I'm talking about something that's absolutely portable. If you
create an empty base class called "poly" with a virtual destructor, and
use it as a virtual base class for all your classes, then there will be
one "poly" handle by which each object can be manipulated. In this case,
dynamic_cast should correctly be able to cast from a poly* to any
derived type, no matter how deeply buried in the inheritance tree it may
be, as long as the cast isn't ambiguous.

--

Ciao,
Paul


[ 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: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/13
Raw View
In article <slrn6t3sf3.hn3.sbnaran@bardeen.ceg.uiuc.edu>,
sbnaran@KILL.uiuc.edu wrote:
>It seems that you want to change the type of an object dynamically at run
>time.  For example, you want to change an object of type Derived1 to an
>object of type Derived2.  I think this can be done easily within the
>framework of C++.
>
>class SomeDerived
>{
>     private:
>          Derived* d_object;
>
>     public:
>          ...
>
>          SomeDerived& operator=(const Base& b)
>          {
>               // check for assignment to self
>               delete d_object;
>               d_object=b.clone();
>          }
>};

  This method is used too (which I think is called "boxed objects") for
objects that do not need to be fast, and therefore do not mind having the
extra pointer to trace. But for small objects (say one or two words or
something like that) one wants to avoid the extra overhead in time, and so
the method I suggested might be needed.

  Here is additional info on how it might be used: One has a class Data,
the user class with a pointer to a class DataRef which are the handles
with a pointer to a class Base. The derived classes of class Base will be
used to change dynamically in the way you suggested above.

  But for the "unboxed" objects (with no extra pointer), one will instead
enlarge the DataRef handle and put in the data there. All DataRef handles
must have the same size though, because of the way they are allocated by
the GC (say linked lists for handles in use and those not in use).

  Then one wants to take full advantage of the C++ OO features, so that
say it is possible to write the classes Data, DataRef and Base in a
library, and somebody adds a class of unboxed objects later.

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


[ 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: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/13
Raw View
In article <35D1A424.167E@wizard.net>, James Kuyper <kuyper@wizard.net> wrote:
>dynamic_cast<void *> of a base pointer gives you a pointer to the
>most-derived object pointed to, cast to (void *). This only works, of
>course, for polymorphic classes. Is that what you need?

  The problem is that one also needs the opposite conversion,
    void* vp;         // Pointing to the full object of a polymorphic object
    dynamic_cast<B*>(vp);   // B* polymorphic.
Then one could just store the pointers to the full polymorphic objects,
and use the dynamic_cast to get the correct pointer adjustments.

  It might not work allowing this version of dynamic_cast with use on
void* pointers, so one variation that comes to my mind is a special
variation of void* which is the "pointer to the full object of a
polymorphic type" -- call it "poly*". Then one might write
    A* ap = new A();
    B* bp = ap;                         // For example, B a base class of A.
    poly* pp = dynamic_cast<poly*>(ap); // Get full pointer to ap,
                                        // if possible.
    // Later:
    B* bp = dynamic_cast<poly*>(pp);    // Get a pointer to the object which
                                        // should be manipulated.

  If a new object should be allocated in the same given space, then one can use
    class B {
    public:
        void* operator new(size_t s, poly* pp)
        {   if (s > B_max_size_t)  error;
            return pp;   }
    };
The usage would be something like
    class C : public B { ... };
    C* cp = new C();
    poly* pp = dynamic_cast<poly*>(cp); // Instead of B* to keep track of
                                        // dynamic objects, use poly*, which
                                        // does not change even if the type of
                                        // the object changes.
    // Later: Object *cp is mutated into an object of another type:
    class D : public B { ... };
    D* dp = new(pp) D();
    // If the pointer dp no more is available, but one knows that it is of
    // type D, use:
    D* d1p = dynamic_cast<D*>(pp);  // Get a pointer to the D object.

  So this way it is possible to do it without any knowledge of compiler
internal layouts and offsets.

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


[ 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: AllanW@my-dejanews.com
Date: 1998/08/13
Raw View
In article <6qqfas$naq@engnews1.Eng.Sun.COM>,
  stephen.clamage@sun.com (Steve Clamage) wrote:
>
> AllanW@my-dejanews.com writes:
>
> >In article <haberg-0608981417590001@sl68.modempool.kth.se>,
> >  haberg@REMOVE.matematik.su.se (Hans Aberg) wrote:
> >>
> >>   Does the C++ standard guarantee, in the case of sungle inheritance, that
> >> the cast of a pointer to a base class make that pointer to point to the
> >> full object?
>
> >Yes.
>
> Hmmm. I previously answered "no" to that question. The question is
> perhaps ambiguous. Here is how I interpreted the question:
>
> class Base { ... };
> class Derived : public Base { ... };
>
> int main()
> {
>  Derived *dp = new Derived;
>  Base *bp = dp; // convert Derived* to Base*
>  if( (void*)bp == (void*)dp )
>   cout << "yes";
>  else
>   cout << "no";
> }
>
> Will the program print "yes"? It will if the Base subobject has
> the same address as the complete Derived object. But the
> standard does not have such a requirement. It explicitly
> says the layout is unspecified. The ARM also said different
> layouts were allowed.

You answered the question WRT physical concepts, particularly the
bit-pattern formed from the pointer. I answered the question WRT
logical concepts; that is, what object does the pointer point to?
My point can be illustrated by the (far more basic) example:
    #include <iostream>
    struct Base {
        virtual void what() { std::cout << "Base"; }
        int member;
    };
    struct Der : public Base {
        void what() { std::cout << "Der"; }
        int member2;
    };
    int main() {
        Der d; // Variable "d" is the "full object" of type Der
        Der *dp = &d;   // dp points to the "full object"
        Base *bp = dp;  // bp also points to the "full object"
        // On some implementations, bp includes an appropriate offset
        // used to access the Base members. But this is an
        // implementation detail, and we should strive to make our
        // program immune to changes in implementation details.
        // The fact is, bp points to (some place in) the "full
        // object" d. This can be proven:
        bp->what(); // Displays "Der", not "Base"
    }

> >The compiler can use any layout it wishes. "A" pointers that really point
> >to a "B" object will work correctly, because the compiler automatically
> >"fixes" the address if needed.

This demonstrates what was in my head when I wrote that response.

> If you use a valid cast, yes, the compiler automatically performs
> any adjustment that is needed. But I don't think that is what
> the question was.

It is what I thought he meant. Looking back, I see that the questioner
is the same one that previously asked a lot of questions about playing
"address" games behind the compiler's back. But I didn't notice that
at the time, and I thought he was asking a more elementary question
about legal C++.

> >Consider:
> >    struct Base { };
> >    struct Mid1 : public Base { };
> >    struct Mid2 : public Base { };
> >    struct Der : public Mid1, Public Mid2 { };
> >Each Der object contains *two* Base objects; one is part of Mid1, and
> >the other is part of Mid2. If a Base pointer really points to a Der
> >object, the compiler will know how to down-cast to Der*.
>
> No, a Base* cannot point to a Der. It can point to one of the
> Base subobjects of a Der.

Of course it can. Prove it the same way as above: add a virtual
function to Base and override it in Der. Again, the actual bit-pattern
in the pointer is of no consequence to me.

> Attempting to cast directly from
> a Base* to a Der* is inherently ambiguous, and will be rejected
> by the compiler. Casting directly from Der* to Base* is
> similarly ambiguous and will be rejected.

Oops! When I said "the compiler will know how to down-cast" I had
my foot in my mouth. I meant something like "there are legal ways
to down-cast from Base* to Der*, provided you know which of Der's
Base's you're pointing to." Referring, of course, to either
explicit static_cast's through Mid1 or Mid2, or one dynamic_cast.

> You can cast via Mid1 or Mid2 to make the cast unambiguous.
> If the classes are polymorphic you can use dynamic_cast to
> get from a base class to the complete object.

I should have said that.

--
AllanW@my-dejanews.com is a "Spam Magnet" -- never read.
Please reply in USENET only, sorry.

-----== Posted via Deja News, The Leader in Internet Discussion ==-----
http://www.dejanews.com/rg_mkgrp.xp   Create Your Own Free Member Forum


[ 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@fermi.ceg.uiuc.edu (Siemel Naran)
Date: 1998/08/13
Raw View
On 13 Aug 1998 16:51:23 GMT, Hans Aberg
>In article <slrn6t3sf3.hn3.sbnaran@bardeen.ceg.uiuc.edu>,

>>class SomeDerived
>>{
>>     private:
>>          Derived* d_object;

>  This method is used too (which I think is called "boxed objects") for
>objects that do not need to be fast, and therefore do not mind having the
>extra pointer to trace. But for small objects (say one or two words or
>something like that) one wants to avoid the extra overhead in time, and so
>the method I suggested might be needed.

What is this extra pointer you talk of?  &SomeDerived should be as
&SomeDerived::d_object.  The option of allocating char[] memory in
advance has the further advantage of faster allocations.


--
----------------------------------
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: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/14
Raw View
  I will explain why I think that the suggestion by Siemel Naran
<sbnaran@ceg.uiuc.edu> does not work. It has to do with the offsets
computed when converted to a base class (i.e. the fact that pointers to a
polymorphic object need not point at the full object).

  This will also explain why I think C++ needs to be extended in this
respect, making the feature possible (perhaps using a "pointer to the full
object of a polymorphic type", as I suggested in another post.

  So, there is a base class B, and two derived classes D1 : B, and D2 : B.
All these objects will get the same memory allocations, and part of the
problem is mutate a D1 object into a D2 object. But is that was all, the
problem would have been easy to solve:

  The objects also have pointers of type B* to them, and to make things
worse, there are several such independent pointers to the object. So when
a D1 object is mutated into a D2 object, it is not sufficient to just
write the D2 object into the space that D1 already has, it must be fit
into the place in such a way that the old B* pointers now point at D2.

  Say we have pointers
    B* bp;
    D1* d1p;
    D2* d2p;
    void* v1p = d1p;
    void* v2p = d2p;
were it is required that bp = d1p and bp = d2p. Then the picture is this:

                   v2p --> ----
 - - - - v1p -> ----  - - |    | - - - - - - -
               |    |     |    |<-- d2p    |
  B* bp ------>| D1 |---->| D2 |           | Memory allocation that
        d1p -->|    |     |    |           | the D1 object gets.
                ----       ----            |
                                           v
 - - - - - - - - - - - - - - - - - - - - - - -

  So the problem is that if we fit the D2 object into the space that the
D1 object has already gotten in such a way that their void* pointers v1p
and v2p agree, then the dynamic_cast<D1*>(d1p) and dynamic_cast<D2*>(d2p)
will compute to different values, which is not allowed in the application
intended.

  Then my idea of C++ extension is this: Instead of using the polymorphic
pointer B* bp, introduce a new polymorphic variation of void*, which I
call "poly*", which has the same value as the void* but with the
additional polymorphic information so that it works with dynamic_cast.

  Then, in the example above, we get
    poly* pp
were it is required that pp = d1p and pp = d2p. But in this case this is
not any problem as these are the pointers to the full object. So the
picture now becomes:

 - pp = v1p --> ----  - -  ---- <-- v2p = pp - -
               |    |     |    |             |
  B* bp ------>| D1 |---->| D2 |<-- d2p      | Memory allocation that
        d1p -->|    | bp->|    |             | the D1 object gets.
                ----      |    |             |
                           ----              v
 - - - - - - - - - - - - - - - - - - - - - - - -

So, in this case it is possible to simply write the D2 object into the
space of the D1 object so that the void* pointers v1p and v2p agree: The
dynamic_cast<D1*>(d1p) and dynamic_cast<D2*>(d2p) computes to different
values, but instead of using the polymorphic B* bp value as a reference to
the D1 and D2 objects, one uses the polymorphic poly* pp value, which does
not change. If one needs to access these objects, with the pp pointer in
hand, one simply writes
    D1* d1p = dynamic_cast<D1*>(pp);    // Access D1 object.
    B* bp = dynamic_cast<D1*>(pp);      // Polymorphic B* pointer of D1.
    // Change to D2
    D2* d1p = dynamic_cast<D2*>(pp);    // Access D2 object.
    B* bp = dynamic_cast<D2*>(pp);      // Polymorphic B* pointer of D2.

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


[ 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@bardeen.ceg.uiuc.edu (Siemel Naran)
Date: 1998/08/14
Raw View
On 14 Aug 1998 16:00:12 GMT, Hans Aberg <haberg@REMOVE.matematik.su.se> wrote:

>  So, there is a base class B, and two derived classes D1 : B, and D2 : B.
>All these objects will get the same memory allocations, and part of the
>problem is mutate a D1 object into a D2 object. But is that was all, the
>problem would have been easy to solve:

You want to change the D1 part of an object into a D2 part without changing
the B part.  This means changing the vptr of the object: from a pointer to
the vtable of class D1 to a pointer to the vtable of class D2.  In C++,
the vptr has type "vtable *const", so it can't be changed.

We can, of course, do this using existing techniques in C++.  Just create
a class Dn which is derived from B and which contains a pointer to a D
object, where D1 and D2 derive from D.  But this means that if we call a
virtual function in the base class B, we'll have to dereference two vptr's:
one to get from B to Dn, and two to get from the D in Dn to D1 or D2.

Maybe if C++ supported a syntax like
     class D : public B, public union { D1,D2 }
we wouldn't have the problem.  This syntax introduces a lot of complexities
into the language.  Maybe in five years?


--
----------------------------------
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: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/14
Raw View
In article <slrn6t8vus.np0.sbnaran@bardeen.ceg.uiuc.edu>,
sbnaran@KILL.uiuc.edu wrote:
>>  So, there is a base class B, and two derived classes D1 : B, and D2 : B.
>>All these objects will get the same memory allocations, and part of the
>>problem is mutate a D1 object into a D2 object. But is that was all, the
>>problem would have been easy to solve:
>
>You want to change the D1 part of an object into a D2 part without changing
>the B part.  This means changing the vptr of the object: from a pointer to
>the vtable of class D1 to a pointer to the vtable of class D2.  In C++,
>the vptr has type "vtable *const", so it can't be changed.

  No, the B part will normally change too (that it, its contents): The
only thing that matters is that the offset that D1 has relative to B is
different from the offset that D2 has relative to B. So when D2 is written
into the same space as D1 so that thier void* pointers agree (pointers to
the whole object), then the old B* pointer to the D1 do not any longer
point at the new D2 object, because the offset is different. Actually, if
the contents of the B part is the same, it has now moved to a new location
(as its offset in D2 is deffernt from that in D1).

>We can, of course, do this using existing techniques in C++.  Just create
>a class Dn which is derived from B and which contains a pointer to a D
>object, where D1 and D2 derive from D.

  The problem is this: The B* pointer is stored and used as a reference to
the object at several independent places, and there is no way B, D1, D2,
or any other class in the hierarchy can control them. So putting in an
extra derived class D will not help: Some other object will just have a
pointer to the object which once was a D1 but now has changed into a D2,
and the pointer (because of a changed offset) has been outdated.

  I could store the void* pointer which does not change in the mutation D1
-> D2, but how do I then find the B* pointer, or for the matter the D2*
pointer: As the situation is dynamic, I do not know what is stored there,
and can only access the object via vbirtual functions. But these are
inaccessible, as I do not know the B* pointer.

>Maybe if C++ supported a syntax like
>     class D : public B, public union { D1,D2 }
>we wouldn't have the problem.  This syntax introduces a lot of complexities
>into the language.  Maybe in five years?

  I thought of that, but that becomes too complicated. It is better
improving on the current derived classes structures, at least for a start.

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


[ 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@bardeen.ceg.uiuc.edu (Siemel Naran)
Date: 1998/08/14
Raw View
On 14 Aug 1998 20:15:49 GMT, Hans Aberg <haberg@REMOVE.matematik.su.se> wrote:

>  No, the B part will normally change too (that it, its contents): The
>only thing that matters is that the offset that D1 has relative to B is
>different from the offset that D2 has relative to B. So when D2 is written
>into the same space as D1 so that thier void* pointers agree (pointers to
>the whole object), then the old B* pointer to the D1 do not any longer
>point at the new D2 object, because the offset is different. Actually, if
>the contents of the B part is the same, it has now moved to a new location
>(as its offset in D2 is deffernt from that in D1).

I don't think this should be allowable.  If B derives from A and if the
standard requires that the internal memory layout should be as follows:
A part first, B part next -- then certainly we can mutate a B1 object
into a B2 object while leaving any pointers A* intact.  In fact, this
is how many (most?) implementations do it.

What you're doing is mutating the most derived sub-object, and it can be
made to work (although there is some difficulty in figuring out the sizeof
of the objects in question).  But what happens if we want to mutate the A1
sub-object into an A2 sub-object?  Or what if C derives from B derives from
A, and we want to mutate the B1 sub-object into a B2 sub-object?  Now the
above memory layout restriction doesn't help us: there might not be enough
space for the new A2 or the new B2 object.

The only way to support dynamic inheritance is, I think, through union
inheritance.  This way, we can get the sizeof's right.  So when we say
"class D : public union { D1,D2 } D" or something like that, the compiler
knows: (1) that we intend to mutate between D1 and D2, and (2) that
sizeof(D)=max(sizeof(D1),sizeof(D2)).


--
----------------------------------
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: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/15
Raw View
In article <slrn6t9c67.np0.sbnaran@bardeen.ceg.uiuc.edu>, sbnaran@uiuc.edu
wrote:
>On 14 Aug 1998 20:15:49 GMT, Hans Aberg <haberg@REMOVE.matematik.su.se> wrote:
>
>>  No, the B part will normally change too (that it, its contents): The
>>only thing that matters is that the offset that D1 has relative to B is
>>different from the offset that D2 has relative to B. So when D2 is written
>>into the same space as D1 so that thier void* pointers agree (pointers to
>>the whole object), then the old B* pointer to the D1 do not any longer
>>point at the new D2 object, because the offset is different. Actually, if
>>the contents of the B part is the same, it has now moved to a new location
>>(as its offset in D2 is deffernt from that in D1).
>
>I don't think this should be allowable.

  My guess is that you are saying that moving the B part when D2 writes
over D1 should not be allowable. The thing is that with multiple
inheritance, there is nothing one can do about it, so therefore I arrived
at the suggestion that there should a polymorhic pointer to the object
which does not change (the "polymorhic pointer to the full object" I
called "poly*").

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


[ 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: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/12
Raw View
In article <6qqfas$naq@engnews1.Eng.Sun.COM>, stephen.clamage@sun.com
(Steve Clamage) wrote:
> The question is
>perhaps ambiguous. Here is how I interpreted the question:
>
>class Base { ... };
>class Derived : public Base { ... };
>
>int main()
>{
>        Derived *dp = new Derived;
>        Base *bp = dp; // convert Derived* to Base*
>        if( (void*)bp == (void*)dp )
>                cout << "yes";
>        else
>                cout << "no";
>}
>
>Will the program print "yes"? It will if the Base subobject has
>the same address as the complete Derived object. But the
>standard does not have such a requirement. It explicitly
>says the layout is unspecified. The ARM also said different
>layouts were allowed.

  This is interpretation I had in my mind of the question I asked. (But if
there are alternative interpretations, it might be interesting to fix that
too.)

  I should perhaps give some context of my question: The idea is to make
"automatic" objects behave as if polymorhic, instead of using a union. (In
order to create an implementation of "unboxed" objects.)

  Then in the base class B, define a "B::operator new" which allocates a
fixed amount of memory (which will in reality be a part of an array);
exceeding this memory is an error. Then from this class derive new
classes. If these do not exceed the fixed memory allocation that one can
get, then one can write
    class C : public B { ... };
    C* cp = new C();
    B* bp = cp;

  Now, for this to work with "unboxed" elements, the pointer bp is not
allowed to change, even if one later may overwrite the memory allocaton
that the object *cp got: I am not speculating how this should be done;
perhaps one can attempt
    void* B::operator new(size_t s, void* vp)
    { if (s <= max_B_size)  return vp; else error; }
just returning the old memory allocation.

  The thing is that when writing
    class D : public B { ... };
    D* dp = new(cp) D();        // Note cp is converted to a void* =
                                // pointer to full object.
then, in the C++ sense, the old bp does no longer point at dp (and the old
object *cp does no longer exist), because C++ does not guarantee it: The
bp restricted from a dp may be different than a bp restricted from a cp.

  It would have worked if I know that the compiler, in the case of single
inheritance, the restricted pointer always point at the full object.

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


[ 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: 1998/08/12
Raw View
haberg@REMOVE.matematik.su.se (Hans Aberg) writes:
>  Does the C++ standard guarantee, in the case of single inheritance, that
>the cast of a pointer to a base class make that pointer to point to the
>full object?

dynamic_cast<void *> of a base pointer gives you a pointer to the
most-derived object pointed to, cast to (void *). This only works, of
course, for polymorphic classes. Is that what you need?


[ 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@bardeen.ceg.uiuc.edu (Siemel Naran)
Date: 1998/08/12
Raw View
On 12 Aug 1998 16:48:13 GMT, Hans Aberg

>  I should perhaps give some context of my question: The idea is to make
>"automatic" objects behave as if polymorhic, instead of using a union. (In
>order to create an implementation of "unboxed" objects.)

It seems that you want to change the type of an object dynamically at run
time.  For example, you want to change an object of type Derived1 to an
object of type Derived2.  I think this can be done easily within the
framework of C++.

class SomeDerived
{
     private:
          Derived* d_object;

     public:
          ...

          SomeDerived& operator=(const Base& b)
          {
               // check for assignment to self
               delete d_object;
               d_object=b.clone();
          }
};

Deleting and newing memory can be expensive.  So this might help

class SomeDerived
{
     private:
          char d_object[MAXSIZE];

     public:
          ...

          SomeDerived& operator=(const Base& b)
          {
               // check for assignment to self
               Base* object=reinterpret_cast<Base*>(d_object);
               object->~Base(); // destroy object but don't release memory
               b.clone(object); // this function clones object in place
          }
};


The constant enum MAXSIZE is an integral constant:
   MAXSIZE  =  max( sizeof(Derived1), sizeof(Derived2), sizeof(Derived3) ).

Two challenges:
(1) make MAXSIZE an integral constant -- maybe template meta-programming?
(2) make it ignorant of the specific Derived objects (i.e. SomeDerived.h
    must not include Derived?.h)

(1) The maximum of three numbers a,b,c is max(max(a,b),c).
    And max(a,b) is (a>b?a:b) which is an integral constant if a,b are.

template<int a, int b> struct Max { enum { max=(a>b?a:b); } };

template<class Max, int b> struct Max
{
     enum { max = (Max::max>b ? Max::max : b) };
};

  Max < Max<2,3>,4 >

  Max<2,3>           this is Max<int=2,int=3> and has Max::max=3
  Max < Max<2,3>,4 > this is Max<class=Max<int,int>,int=4> and has Max::max=4

Anyway, I don't think template overloading is in C++, although it's not a bad
idea.



(2) ?



--
----------------------------------
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: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/11
Raw View
  Does the C++ standard guarantee, in the case of sungle inheritance, that
the cast of a pointer to a base class make that pointer to point to the
full object?

  Reading ARM 1994 can be somewhat confusing:

  On the one hand, given "class B : A", ch 10.1c says that an object may
(in principle) be layed out as
    B part
    A part
even such a layout is uncommon. Then, when casting Bp* = new B(); A* ap =
bp; it seems that ap will not point to the full object (the value of bp).

  On the other hand, ch 10.3c uses the wording "With multiple inheritance,
casting may change the value of the pointer", which gives the impression
that it cannot happen in the case of single inheritance.

  So what is true?

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


[ 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: stephen.clamage@sun.com (Steve Clamage)
Date: 1998/08/11
Raw View
haberg@REMOVE.matematik.su.se (Hans Aberg) writes:


>  Does the C++ standard guarantee, in the case of single inheritance, that
>the cast of a pointer to a base class make that pointer to point to the
>full object?

There are no guarantees about relative positions of base classes
in an object. The standard explictly says the layout is unspecified,
which means the implementation doesn't even have to tell you what
that layout is.

You cannot assume even for single inheritance that no pointer
adjustment will occur when casting between Derived* and Base*.

It is common but not required that for single inheritance the
address of any base class is the same as the complete object.
Some implementations put the base part after the derived
part for various reasons, in which case every cast involves a
pointer adjustment.

--
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: AllanW@my-dejanews.com
Date: 1998/08/11
Raw View
In article <haberg-0608981417590001@sl68.modempool.kth.se>,
  haberg@REMOVE.matematik.su.se (Hans Aberg) wrote:
>
>   Does the C++ standard guarantee, in the case of sungle inheritance, that
> the cast of a pointer to a base class make that pointer to point to the
> full object?

Yes.

>   Reading ARM 1994 can be somewhat confusing:
>
>   On the one hand, given "class B : A", ch 10.1c says that an object may
> (in principle) be layed out as
>     B part
>     A part
> even such a layout is uncommon. Then, when casting Bp* = new B(); A* ap =
> bp; it seems that ap will not point to the full object (the value of bp).

The compiler can use any layout it wishes. "A" pointers that really point
to a "B" object will work correctly, because the compiler automatically
"fixes" the address if needed.

>   On the other hand, ch 10.3c uses the wording "With multiple inheritance,
> casting may change the value of the pointer", which gives the impression
> that it cannot happen in the case of single inheritance.

Consider:
    struct Base { };
    struct Mid1 : public Base { };
    struct Mid2 : public Base { };
    struct Der : public Mid1, Public Mid2 { };
Each Der object contains *two* Base objects; one is part of Mid1, and
the other is part of Mid2. If a Base pointer really points to a Der
object, the compiler will know how to down-cast to Der*. There are
two different Base* values that we could have started with, but both
will end up being the same after down-casting to Der*. At least one
of them has changed value during this cast.

--
AllanW@my-dejanews.com is a "Spam Magnet" -- never read.
Please reply in USENET only, sorry.

-----== Posted via Deja News, The Leader in Internet Discussion ==-----
http://www.dejanews.com/rg_mkgrp.xp   Create Your Own Free Member Forum


[ 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: haberg@REMOVE.matematik.su.se (Hans Aberg)
Date: 1998/08/11
Raw View
In article <6qpvee$jqd@engnews1.Eng.Sun.COM>, stephen.clamage@sun.com
(Steve Clamage) wrote:
>There are no guarantees about relative positions of base classes
>in an object. The standard explictly says the layout is unspecified,
>which means the implementation doesn't even have to tell you what
>that layout is.
...
>It is common but not required that for single inheritance the
>address of any base class is the same as the complete object.
>Some implementations put the base part after the derived
>part for various reasons, in which case every cast involves a
>pointer adjustment.

  I had a hunch that this probably was the case.

  But it means that specialty implementations, like "unboxed objects",
cannot be done in C++ by taking advantage of its OO features.

  So if there ever is gonna be a new version of C++, I think it would be
good if such issues would be considered: Either one should be able to
choose implementation method at need, or C++ should be augmented with a
few more polymorphic types (apart from the virtual pointer to a class
object) fulfilling the needs.

  Hans Aberg   * Anti-spam: Remove "REMOVE." from email address.
               * Email: Hans Aberg <haberg@REMOVE.member.ams.org>
               * Home Page: <http://www.matematik.su.se/~haberg/>
               * AMS member listing: <http://www.ams.org/cml/>


[ 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: stephen.clamage@sun.com (Steve Clamage)
Date: 1998/08/11
Raw View
AllanW@my-dejanews.com writes:


>In article <haberg-0608981417590001@sl68.modempool.kth.se>,
>  haberg@REMOVE.matematik.su.se (Hans Aberg) wrote:
>>
>>   Does the C++ standard guarantee, in the case of sungle inheritance, that
>> the cast of a pointer to a base class make that pointer to point to the
>> full object?

>Yes.

Hmmm. I previously answered "no" to that question. The question is
perhaps ambiguous. Here is how I interpreted the question:

class Base { ... };
class Derived : public Base { ... };

int main()
{
 Derived *dp = new Derived;
 Base *bp = dp; // convert Derived* to Base*
 if( (void*)bp == (void*)dp )
  cout << "yes";
 else
  cout << "no";
}

Will the program print "yes"? It will if the Base subobject has
the same address as the complete Derived object. But the
standard does not have such a requirement. It explicitly
says the layout is unspecified. The ARM also said different
layouts were allowed.


>The compiler can use any layout it wishes. "A" pointers that really point
>to a "B" object will work correctly, because the compiler automatically
>"fixes" the address if needed.

If you use a valid cast, yes, the compiler automatically performs
any adjustment that is needed. But I don't think that is what
the question was.

>Consider:
>    struct Base { };
>    struct Mid1 : public Base { };
>    struct Mid2 : public Base { };
>    struct Der : public Mid1, Public Mid2 { };
>Each Der object contains *two* Base objects; one is part of Mid1, and
>the other is part of Mid2. If a Base pointer really points to a Der
>object, the compiler will know how to down-cast to Der*.

No, a Base* cannot point to a Der. It can point to one of the
Base subobjects of a Der. Attempting to cast directly from
a Base* to a Der* is inherently ambiguous, and will be rejected
by the compiler. Casting directly from Der* to Base* is
similarly ambiguous and will be rejected. You can cast via
Mid1 or Mid2 to make the cast unambiguous.

If the classes are polymorphic you can use dynamic_cast to
get from a base class to the complete object.

--
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              ]