#include <cassert>

// ----------------------------------------------------------------
// Library code
// ----------------------------------------------------------------
class NoBase {};

template <typename T> struct Base { typedef NoBase type; };

template<typename T, typename U=T>
class SmartPtr : public SmartPtr<T, typename Base<U>::type>
{
private:
  template <typename, typename> friend class SmartPtr;
  SmartPtr () {}
  const SmartPtr<T>& self () const
  { return static_cast<const SmartPtr<T>&>(*this); }
public:
  T* operator->() const { return self().operator->(); }
  T& operator* () const { return self().operator* (); }
};

template <typename T>
class SmartPtr<T,T> : public SmartPtr<T, typename Base<T>::type>
{
public:
  SmartPtr (T* p = 0) : p_ (p) {}

//   // Copy constructor cannot be a template.
//   SmartPtr (const SmartPtr& x)
//     : p_ (x.operator->()) {}

  template <typename U, typename V>
  SmartPtr (const SmartPtr<U,V>& x)
    : p_ (x.operator->()) {}

//   // Copy assignment operator cannot be a template.
//   SmartPtr& operator= (const SmartPtr& x)
//   { p_ = x.operator->(); return *this; }

  template <typename U, typename V>
  SmartPtr& operator= (const SmartPtr<U,V>& x)
  { p_ = x.operator->(); return *this; }

  T* operator->() const { return  p_; }
  T& operator* () const { return *p_; }

private:
  T *p_;
};

template <typename T> class SmartPtr<T,NoBase> {};

// ----------------------------------------------------------------
// Sample Client code
// ----------------------------------------------------------------
struct A            { int s() { return 1; } virtual int v() { return 1; } };
struct B : public A { int s() { return 2; } virtual int v() { return 2; } };
struct C : public B { int s() { return 3; } virtual int v() { return 3; } };
struct X            { int s() { return 4; } virtual int v() { return 4; } };

template <> struct Base<B> { typedef A type; };
template <> struct Base<C> { typedef B type; };

template <class T> inline int fv (const SmartPtr<T,A>& x) { return x->v(); }
template <class T> inline int fv (const SmartPtr<T,B>& x) { return x->v(); }
template <class T> inline int fv (const SmartPtr<T,X>& x) { return x->v(); }

template <class T> inline int fs (const SmartPtr<T,A>& x) { return x->s(); }
template <class T> inline int fs (const SmartPtr<T,B>& x) { return x->s(); }
template <class T> inline int fs (const SmartPtr<T,X>& x) { return x->s(); }

template <class T, class U>
inline int gv (const SmartPtr<T,U>& x) { return x->v(); }

template <class T, class U>
inline int gs (const SmartPtr<T,U>& x) { return x->s(); }

inline int hv (const SmartPtr<B>& x) { return x->v(); }

inline int hs (const SmartPtr<B>& x) { return x->s(); }

int main (int argc, char *argv[])
{
  SmartPtr<A> ap = new A ();
  SmartPtr<B> bp = new B ();
  SmartPtr<C> cp = new C ();
  SmartPtr<X> xp = new X ();

  assert (fv (ap) == 1);
  assert (fv (bp) == 2);
  assert (fv (cp) == 3);
  assert (fv (SmartPtr<B>(cp)) == 3);
  assert (fv (SmartPtr<B>(bp)) == 2);
  assert (fv (xp) == 4);

  assert (fs (ap) == 1);
  assert (fs (bp) == 2);
  assert (fs (cp) == 3);
  assert (fs (SmartPtr<B>(cp)) == 2);
  assert (fs (SmartPtr<B>(bp)) == 2);
  assert (fs (xp) == 4);

  assert (ap->v() == 1);
  assert (bp->v() == 2);
  assert (cp->v() == 3);
  assert (xp->v() == 4);

  assert (ap->s() == 1);
  assert (bp->s() == 2);
  assert (cp->s() == 3);
  assert (xp->s() == 4);

  assert (gv (ap) == 1);
  assert (gv (bp) == 2);
  assert (gv (cp) == 3);
  assert (gv (xp) == 4);

  assert (gs (ap) == 1);
  assert (gs (bp) == 2);
  assert (gs (cp) == 3);
  assert (gs (xp) == 4);

  SmartPtr<A> ap2 (ap); ap2 = bp; assert (fv (ap2) == 2);
  SmartPtr<B> bp2 (bp); bp2 = cp; assert (fv (bp2) == 3);
  SmartPtr<C> cp2 (cp); cp2 = cp; assert (fv (cp2) == 3);
  SmartPtr<X> xp2 (xp); xp2 = xp; assert (fv (xp2) == 4);

  assert (fs (ap2) == 1);
  assert (fs (bp2) == 2);
  assert (fs (cp2) == 3);
  assert (fs (xp2) == 4);

  assert (sizeof (SmartPtr<A>) == sizeof (A*));
  assert (sizeof (SmartPtr<B>) == sizeof (B*));
  assert (sizeof (SmartPtr<C>) == sizeof (C*));

  assert (hv (bp) == 2);
  assert (hv (cp) == 3);

  assert (hs (bp) == 2);
  assert (hs (cp) == 2);

  assert (hv (bp2) == 3);
  assert (hv (cp2) == 3);

  assert (hs (bp2) == 2);
  assert (hs (cp2) == 2);

  return 0;
}
