From: jamshid@emx.cc.utexas.edu (Jamshid Afshar)
Newsgroups: comp.lang.c++
Subject: Simulating |Derived*| to |Base*| conversions for smart pointers
Summary: long explanation and code using templates
Keywords: advanced,templates,pointers,handles,conversions
Date: 14 Oct 92 02:38:05 GMT
Reply-To: jamshid@emx.utexas.edu
Organization: The University of Texas at Austin; Austin, Texas

Someone recently posted about a problem when using a pointer-like
class (aka handle or smart pointer) on objects in a class hierarchy.
He could not find a way for smart pointers to simulate the automatic
|Derived*| to |Base*| conversions of real pointers.

      template<class T> class Ptr {
      public:
         Ptr();
         Ptr(T* p);
         //... no "operator T*()" conversion
      };
      class Base {};
      class Derived : public Base {};

      Base* b = new Base;
      Derived* d = b;            // okay
      Ptr<Base> bp = new Base;
      Ptr<Derived> dp = bp;      // error

In this article I will present a method which peforms automatic
conversions from Ptr<Derived> to Ptr<Base> so code like the last line
above compiles and works as expected.  Let me start by reminding you
that there's no free lunch.  This method will require a little bit of
work from you, the maintainer of <Ptr.h> or <Derived.h>.

   o You must make a few modifications to Ptr<T> in <Ptr.h>.
   o You must add a few small classes and possibly functions to your
     <Ptr.h> file.
   o For the classes (and only those classes) which you want these
     Ptr<Derived> to Ptr<Base> conversions, you must add a small
     class definition at the end of <Derived.h> which indicates all
     the bases to which a Derived can convert.
   o Unfortunately this method does not perform anything like
     determining "better" derived to base conversions:
         class Derived2 : Derived {};
         void f(Ptr<Base>);
         void f(Ptr<Derived>);
         Ptr<Derived2> p;
         ... f(p);  // error: ambiguous (you must explicitly cast)

The advantages of this method are that it does not introduce any
run-time overhead to existing code and the modifications are rather
small, simple and contained.  All the code is completely type-safe 
(checked at compile time).  You continue using Ptr<T> as you always
had.  Here's what you have to do:

1. In <Ptr.h> rename "Ptr<T>" to "Ptr0<T>".
2. Also in <Ptr.h>, create a new Ptr<T> template which inherits from
   Ptr0<T>.  The new Ptr<T> must redefine any members which are not
   inherited or generated (eg, special ctors and assignment operators).
3. Depending on the Ptr0 class, "operator X()" conversions, and its use
   of overloading, you may also have to define new versions of member
   or friend functions which currently take or return a Ptr0 (eg,
   operator==(), clone()) to use a Ptr.  Possibly, you could instead
   define a Ptr<T>::Ptr(const Ptr0<T>&) ctor.

I believe defining a working
      int operator==(Ptr<T>,Ptr<T>)
function when an
      operator Ptr0<T>::void*()
function also exists is about as problematic as step #3 can get
because of all the possible ambiguity errors when we perform step #4
below.  I'll show my solution to the "==" problem later.  Note the
solution to the original problem would be much simpler and less
verbose if operator==() were not a requirement.  Furthermore, it would
probably open the door to lots of other solutions.  Btw, for brevity's
sake (who for the most part I have forsaken :-) I've left operator!=()
as an exercise for the reader.  Actually, I think a simple global
      template<class T>
      int operator!=(const T& a, const T& b) { return !(a==b); }
might do.

      // the new Ptr.h after steps #1 and #2
      // a simple NULL-checking smart pointer class
      template<class T> class Ptr0 {
      public:
         Ptr0()
            : _p(0) {}
         Ptr0(T* p)
            : _p(p) {}
         Ptr0(const Ptr0<T>& ptr)
            : _p(ptr._p) {}
         ~Ptr0()
            {}

         void operator=(const Ptr0<T>& ptr)
            { _p = ptr._p; }

         friend int operator==(const Ptr0<T>& a, const Ptr0<T>& b)
            { return a._p==b._p; }
         friend int operator==(const Ptr0<T>& a, const T* b)
            { return a._p==b; }
         friend int operator==(const T* b, const Ptr0<T>& a)
            { return b==a; }

         T* operator->() const
            { assert(_p != 0); return _p; }
         T& operator*() const
            { assert(_p != 0); return *_p; }

         operator void*() const
            { return (void*)_p; }
         int operator!() const
            { return _p ? 0 : 1; }

      protected:
         T* _p;
      };

      template<class T> struct Ptr : public Ptr0<T> {
         Ptr() {};
         Ptr(T* p) : Ptr0<T>(p) {};
      };

      template<class T> inline
      int operator==(const Ptr<T>& a, const Ptr<T>& b)
         { return (const Ptr0<T>&)a==(const Ptr0<T>&)b; }
      template<class T> inline
      int operator==(const Ptr<T>& a, const T* b)
         { return (const Ptr0<T>&)a==b; }
      template<class T> inline
      int operator==(const T* a, const Ptr<T>& b)
         { return b==a; }


Okay, that's all the required modifications to your original Ptr
class.  Current users of <Ptr.h> should still be able to compile
without any problems.  Now on to step #4.

4. Derive from Ptr0 the new classes Ptr1 through PtrN.  A Ptr1<T,B1> is
   used for a class T which has one base class, B1.  A Ptr2<T,B2,B1> is
   used for a T which inherits from B1 and B2 (either single or multiple
   inheritance), etc.  I'll show the definitions for Ptr1 and Ptr2.  With
   a good editor, it's trivial to add many more to <Ptr.h>.

   // at the end of <Ptr.h>
   template<class T, class B1>
   struct Ptr1 : public Ptr0<T> {
      Ptr1() {}
      Ptr1(T* p) : Ptr0<T>(p) {}
      operator Ptr<B1>() const { return Ptr<B1>(_p); }
      friend int operator==(const Ptr<B1>& a, const Ptr<T>& b)
         { return a==(Ptr<B1>)b; }
      friend int operator==(const Ptr<T>& a, const Ptr<B1>& b)
         { return b==a; }
   };

   template<class T, class B1, class B2>
   struct Ptr2 : public Ptr1<T,B2> {
      Ptr2() {}
      Ptr2(T* p) : Ptr1<T,B2>(p) {}
      operator Ptr<B1>() const { return Ptr<B1>(_p); }
      friend int operator==(const Ptr<B1>& a, const Ptr<T>& b)
         { return a==(Ptr<B1>)b; }
      friend int operator==(const Ptr<T>& a, const Ptr<B1>& b)
         { return b==a; }
   };

Ignore the operator==() functions for now.  Each PtrI class is derived
from Ptr[I-1], right down to Ptr0<T>.  PtrI<T,B1,..,BI> defines one
"operator B1()" conversion.  By inheriting from Ptr[I-1]<T,B2,...,BI>,
PtrI can automaticly convert to any of the classes B1..BN.  Pretty
clever, huh?

5. Now we perform the modifications in each <Derived.h>.  For each
   Derived class you can define a "specialized" version of Ptr which
   instead of inheriting from Ptr0<T> (which would mean T has zero
   base classes), inherits from PtrI<T,B1,...BI> which means Ptr<T>
   can convert to Ptr<B1>...Ptr<BI>.

   // new Derived.h
   class Derived : public Base { /*...*/ };

   // a "specialized" version of Ptr (use this definition instead of Ptr<T>)
   struct Ptr<Derived> : public Ptr1<Derived,Base> {
      Ptr() {}
      Ptr(T* p) : Ptr1<Derived,Base>(p) {}
      // anything else not inherited (eg, special operator=()'s)
   };

   [btw, at first this might look like a candidate for the recently
   suggested "template typedefs", but I don't think they could be
   used in this technique]

I'll now discuss the problem with the equality operator.  Since we allow:
   Ptr<Base> b;
   Ptr<Derived> d; 
   b = d;
We should also allow:
   if (b==d) ...

Simple, right?  'd' should just convert to a Ptr<Base> and
   int operator==(const Ptr<Base>&,const Ptr<Base>&);
from <Ptr.h> should be called.  Unfortunately, no.  The
"Ptr0<T>::operator void*()" conversion gets in the way and causes
ambiguity with the built-in "operator==(void*,void*)".  So,
PtrI<T,...> also defines an equality operator for a Ptr<T> and
Ptr<B1> that first casts Ptr<T> to a Ptr<B1> and then performs the
comparison.  Btw, your compiler might have problems with this.  See
the appended code for my BC++ 3.1 workaround.

I admit the specialized definition of Ptr for each derived class is
tedious, but at least you don't pay unless you want to play: if you
don't care about assigning Ptr<DerivedX> to Ptr<Base>, you don't have
to define the specialized Ptr<DerivedX> class.  If you later decide
that you want this functionality, any code you've written using the
default Ptr<DerivedX> still works (and runs just as quickly) by
simply recompiling.

The only part of this technique I really don't like is the fact that
you must specify *all* the base classes.  I wish that for the hiearchy
   C -> B -> A
Ptr<C> only had to specify B as a base and the fact that A is also a
base would somehow be derived [sic] automaticly.  I doubt that there is
a way to do this, though -- don't forget about MI.

I'm very curious whether others have already discovered this
technique.  If I have overlooked any problems or if you have a
different method of solving the problem, please post or email.
Your opinions and experiences, good or bad, are appreciated.

Appended is a shar file containing the complete example (test.cpp,
classes.h, ptr.h, origptr.h).  Compile test.cpp with "-DSMARTER_PTRS".
I've compiled and run it with BC++ 3.1.  Other compilers, and even
BC++ 3.0, might have problems with this code, both because it contains
some workarounds I had to put in for BC++ bugs, and because it does
some fairly complicated things with templates.  Let me know what has
to be done to get it compiled under other compilers.  I'm curious
whether any current compiler implementing templates is bug-free and
up-to-date enough to work without any of my <ptr.h> workarounds
(they're marked with "##" comments).

Jamshid Afshar
jamshid@emx.utexas.edu

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  test.cpp classes.h origptr.h ptr.h
# Wrapped by jamshid@emx.cc.utexas.edu on Tue Oct 13 21:27:05 1992
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'test.cpp' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'test.cpp'\"
else
echo shar: Extracting \"'test.cpp'\" \(2504 characters\)
sed "s/^X//" >'test.cpp' <<'END_OF_FILE'
X// compile with -DSMARTER_PTRS to use and test the modified ptr.h instead
X// of origptr.h
X
X#include <iostream.h>
X#include <assert.h>
X#include "classes.h"
X
XA a;
XB b;
XC c;
XD d;
X
XPtr<A> aptr;
XPtr<B> bptr;
XPtr<C> cptr;
XPtr<D> dptr;
X
Xmain()
X{
X   cout << "Testing assignment to 0.\n";
X   aptr = 0;
X   bptr = 0;
X   cptr = 0;
X   dptr = 0;
X   assert(!aptr && aptr==0);
X   assert(!bptr && bptr==0);
X   assert(!cptr && cptr==0);
X   assert(!dptr && dptr==0);
X
X   cout << "Testing assignment of &T and &T' to Ptr<T>.\n";
X   aptr = &a;
X   assert(aptr->type()==CLASSA);
X   aptr = &b;
X   assert(aptr->type()==CLASSB);
X   aptr = &c;
X   assert(aptr->type()==CLASSC);
X   aptr = &d;
X   assert(aptr->type()==CLASSD);
X
X   bptr = &b;
X   assert(bptr->type()==CLASSB);
X   bptr = &c;
X   assert(bptr->type()==CLASSC);
X   bptr = &d;
X   assert(bptr->type()==CLASSD);
X
X   cptr = &c;
X   assert(cptr->type()==CLASSC);
X   cptr = &d;
X   assert(cptr->type()==CLASSD);
X
X   dptr = &d;
X   assert(dptr->type()==CLASSD);
X
X   cout << "Testing |void*| conversion and operator '!'.\n";
X   assert(aptr && (void*)aptr && !!aptr);
X   assert(bptr && (void*)bptr && !!bptr);
X   assert(cptr && (void*)cptr && !!cptr);
X   assert(dptr && (void*)dptr && !!dptr);
X
X   cout << "Testing |Ptr<T>|==|T*| and |Ptr<T>|==|T'*|.\n";
X   assert(aptr==&d);
X   assert(bptr==&d);
X   assert(cptr==&d);
X   assert(dptr==&d);
X
X#ifdef SMARTER_PTRS
X   cout << "Testing |Ptr<T>|==|Ptr<T>| and |Ptr<T>|==|Ptr<T'>|.\n";
X   assert(aptr==aptr);
X   assert(aptr==bptr);
X   assert(aptr==cptr);
X   assert(aptr==dptr);
X
X   assert(bptr==bptr);
X   assert(bptr==cptr);
X   assert(bptr==dptr);
X
X   assert(cptr==cptr);
X   assert(cptr==dptr);
X
X   assert(dptr==dptr);
X
X   cout << "Testing assignment of &T to Ptr<T>.\n";
X   aptr = &a;
X   assert(aptr->type()==CLASSA);
X   bptr = &b;
X   assert(bptr->type()==CLASSB);
X   cptr = &c;
X   assert(cptr->type()==CLASSC);
X   dptr = &d;
X   assert(dptr->type()==CLASSD);
X
X   cout << "Testing assignment of Ptr<T> and Ptr<T'> to Ptr<T>.\n";
X   aptr = aptr;
X   assert(aptr->type()==CLASSA);
X   aptr = bptr;
X   assert(aptr->type()==CLASSB);
X   aptr = cptr;
X   assert(aptr->type()==CLASSC);
X   aptr = dptr;
X   assert(aptr->type()==CLASSD);
X
X   bptr = bptr;
X   assert(bptr->type()==CLASSB);
X   bptr = cptr;
X   assert(bptr->type()==CLASSC);
X   bptr = dptr;
X   assert(bptr->type()==CLASSD);
X
X   cptr = cptr;
X   assert(cptr->type()==CLASSC);
X   cptr = dptr;
X   assert(cptr->type()==CLASSD);
X
X   dptr = dptr;
X   assert(dptr->type()==CLASSD);
X#endif
X
X   return 0;
X}
END_OF_FILE
if test 2504 -ne `wc -c <'test.cpp'`; then
    echo shar: \"'test.cpp'\" unpacked with wrong size!
fi
# end of 'test.cpp'
fi
if test -f 'classes.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'classes.h'\"
else
echo shar: Extracting \"'classes.h'\" \(1021 characters\)
sed "s/^X//" >'classes.h' <<'END_OF_FILE'
X// classes.h -- dummy classes A,B,C,D where
X//        A
X//       / \
X//      B   |
X//      |   |
X//      C   |
X//       \ /
X//        D
X#ifndef Classes_H
X#define Classes_H
X
X#ifdef SMARTER_PTRS
X#include "ptr.h"
X#else
X#include "origptr.h"
X#endif
X
Xenum Types { CLASSA, CLASSB, CLASSC, CLASSD };
X
Xclass A {
Xpublic:
X   virtual Types type()
X      { return CLASSA; }
X};
X
Xclass B : virtual public A {
Xpublic:
X   virtual Types type()
X      { return CLASSB; }
X};
X
X#ifdef SMARTER_PTRS
Xstruct Ptr<B> : public Ptr1<B,A> {
X   Ptr<B>() {}
X   Ptr<B>(B* p) : Ptr1<B,A>(p) {}
X};
X#endif
X
Xclass C : public B {
Xpublic:
X   virtual Types type()
X      { return CLASSC; }
X};
X
X#ifdef SMARTER_PTRS
Xstruct Ptr<C> : public Ptr2<C,B,A> {
X   Ptr<C>() {}
X   Ptr<C>(C* p) : Ptr2<C,B,A>(p) {}
X};
X#endif
X
Xclass D : virtual public A, virtual public C {
Xpublic:
X   virtual Types type()
X      { return CLASSD; }
X};
X
X#ifdef SMARTER_PTRS
Xstruct Ptr<D> : public Ptr3<D,C,B,A> {
X   Ptr<D>() {}
X   Ptr<D>(D* p) : Ptr3<D,C,B,A>(p) {}
X};
X#endif
X
X#endif // Classes_H
END_OF_FILE
if test 1021 -ne `wc -c <'classes.h'`; then
    echo shar: \"'classes.h'\" unpacked with wrong size!
fi
# end of 'classes.h'
fi
if test -f 'origptr.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'origptr.h'\"
else
echo shar: Extracting \"'origptr.h'\" \(849 characters\)
sed "s/^X//" >'origptr.h' <<'END_OF_FILE'
X// The original ptr.h
X// ptr.h -- A null-checking Ptr<T> class
X
X#ifndef Ptr_H
X#define Ptr_H
X
X#include <assert.h>
X
Xtemplate<class T> class Ptr {
Xpublic:
X   Ptr()
X      : _p(0) {}
X   Ptr(T* p)
X      : _p(p) {}
X   Ptr(const Ptr<T>& ptr)
X      : _p(ptr._p) {}
X   ~Ptr()
X      {}
X
X   void operator=(const Ptr<T>& ptr)
X      { _p = ptr._p; }
X
X   friend int operator==(const Ptr<T>& a, const Ptr<T>& b)
X      { return a._p==b._p; }
X   friend int operator==(const Ptr<T>& a, const T* b)
X      { return a._p==b; }
X   friend int operator==(const T* b, const Ptr<T>& a)
X      { return b==a; }
X
X   T* operator->()
X      { assert(_p != 0); return _p; }
X   T& operator*()
X      { assert(_p != 0); return *_p; }
X
X   operator void*() const
X      { return (void*)_p; }
X   int operator!() const
X      { return _p ? 0 : 1; }
X
Xprotected:
X   T* _p;
X};
X
X#endif // Ptr_H
X
END_OF_FILE
if test 849 -ne `wc -c <'origptr.h'`; then
    echo shar: \"'origptr.h'\" unpacked with wrong size!
fi
# end of 'origptr.h'
fi
if test -f 'ptr.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'ptr.h'\"
else
echo shar: Extracting \"'ptr.h'\" \(3800 characters\)
sed "s/^X//" >'ptr.h' <<'END_OF_FILE'
X// ptr.h -- A null-checking Ptr<T> class, modified to simulate
X//          Derived* -> Base* conversions
X// The comments marked with "##" indicate BC++ 3.1 bugs.  You might
X// try making sure your compiler works without these workarounds.
X
X#ifndef Ptr_H
X#define Ptr_H
X
X#include <assert.h>
X
Xtemplate<class T> class Ptr0 {
Xpublic:
X   Ptr0()
X      : _p(0) {}
X   Ptr0(T* p)
X      : _p(p) {}
X   Ptr0(const Ptr0<T>& ptr)
X      : _p(ptr._p) {}
X   ~Ptr0()
X      {}
X
X   void operator=(const Ptr0<T>& ptr)
X      { _p = ptr._p; }
X
X   friend int operator==(const Ptr0<T>& a, const Ptr0<T>& b)
X      { return a._p==b._p; }
X   friend int operator==(const Ptr0<T>& a, const T* b)
X      { return a._p==b; }
X   friend int operator==(const T* b, const Ptr0<T>& a)
X      { return b==a; }
X
X   T* operator->() const
X      { assert(_p != 0); return _p; }
X   T& operator*() const
X      { assert(_p != 0); return *_p; }
X
X   operator void*() const
X      { return (void*)_p; }
X   int operator!() const
X      { return _p ? 0 : 1; }
X
Xprotected:
X   T* _p;
X};
X
Xtemplate<class T> struct Ptr : public Ptr0<T> {
X   Ptr() {};
X   Ptr(T* p) : Ptr0<T>(p) {};
X
X   // although the following are defined for Ptr0, they must be
X   // redefined for Ptr to reduce possibily of ambiguity errors
X   /*inline##*/ friend int operator==(const Ptr<T>& a, const Ptr<T>& b);
X   /*inline##*/ friend int operator==(const Ptr<T>& a, const T* b);
X   /*inline##*/ friend int operator==(const T* a, const Ptr<T>& b);
X   //## BC++ 3.1 doesn't allow friends to be declared as inline
X   //## They shouldn't have to be friends, but BC++ 3.1 will otherwise
X   //   ignore them.  (that's why they're also in Ptr1)
X};
X
Xtemplate<class T> inline
Xint operator==(const Ptr<T>& a, const Ptr<T>& b)
X   { return (const Ptr0<T>&)a==(const Ptr0<T>&)b; }
Xtemplate<class T> inline
Xint operator==(const Ptr<T>& a, const T* b)
X   { return (const Ptr0<T>&)a==b; }
Xtemplate<class T> inline
Xint operator==(const T* a, const Ptr<T>& b)
X   { return b==a; }
X
Xtemplate<class B1, class T>
Xstruct Hack {  //## BC++ 3.1 wouldn't find match if eqcvt regular function
Xstatic int eqcvt(const Ptr<B1>& a, const Ptr<T>& b)
X   { return a==(Ptr<B1>)b; }
X};
X
Xtemplate<class T, class B1>
Xstruct Ptr1 : public Ptr0<T> {
X   Ptr1() {}
X   Ptr1(T* p) : Ptr0<T>(p) {}
X   operator Ptr<B1>() const { return Ptr<B1>(_p); }
X   friend int operator==(const Ptr<B1>& a, const Ptr<T>& b)
X   //## I don't think any intermediate function should be necessary,
X   // but BC++ 3.1 confused by "return a==(Ptr<B1>)b;"
X      { return Hack<B1,T>::eqcvt(a,b); }
X   friend int operator==(const Ptr<T>& a, const Ptr<B1>& b)
X      { return b==a; }
X
X   //## I don't believe the following three declarations should be necessary
X   // since the functions are defined above, but apparently BC++ 3.1 does
X   // not match template functions correctly so it requires that they be
X   // expanded forcefully.
X   friend int operator==(const Ptr<T>& a, const Ptr<T>& b);
X   friend int operator==(const Ptr<T>& a, const T* b);
X   friend int operator==(const T* a, const Ptr<T>& b);
X};
X
Xtemplate<class T, class B1, class B2>
Xstruct Ptr2 : public Ptr1<T,B2> {
X   Ptr2() {}
X   Ptr2(T* p) : Ptr1<T,B2>(p) {}
X   operator Ptr<B1>() const { return Ptr<B1>(_p); }
X   friend int operator==(const Ptr<B1>& a, const Ptr<T>& b)
X      { return Hack<B1,T>::eqcvt(a,b); }
X   friend int operator==(const Ptr<T>& a, const Ptr<B1>& b)
X      { return b==a; }
X};
X
Xtemplate<class T, class B1, class B2, class B3>
Xstruct Ptr3 : public Ptr2<T,B2,B3> {
X   Ptr3() {}
X   Ptr3(T* p) : Ptr2<T,B2,B3>(p) {}
X   operator Ptr<B1>() const { return Ptr<B1>(_p); }
X   friend int operator==(const Ptr<B1>& a, const Ptr<T>& b)
X      { return Hack<B1,T>::eqcvt(a,b); }
X   friend int operator==(const Ptr<T>& a, const Ptr<B1>& b)
X      { return b==a; }
X};
X
X#endif // Ptr_H
END_OF_FILE
if test 3800 -ne `wc -c <'ptr.h'`; then
    echo shar: \"'ptr.h'\" unpacked with wrong size!
fi
# end of 'ptr.h'
fi
echo shar: End of shell archive.
exit 0