#include <iostream>
using namespace std;

class parent {
   protected:
      int P;
   public:
      parent(int pval = 0) { P = pval; cout << "Parent constructor " << P << endl; }
      ~parent() { cout << "Parent destructor on " << P << endl; }

      // default, statically bound, method
      void sprint() { cout << "Parent sprint: P = " << P << endl; }

      // virtual method
      virtual void vprint() { cout << "Parent vprint: P = " << P << endl; }
};

class child : public parent {
   protected:
      int C;
   public:
      child(int cval = 1) { C = cval; cout << "Child constructor " << P  << "," << C << endl; }
      ~child() { cout << "Child destructor on " << P  << "," << C << endl; }

      // overrides static parent method
      void sprint() { cout << "Child sprint: P = " << P  << "," << C << endl; }

      // overrides virtual parent method
      virtual void vprint() { cout << "Child vprint: P = " << P  << "," << C << endl; }
};

// can be passed a pointer to a parent or a child
//     ptr->sprint always calls the static parent version since ptr is of type parent*
//     ptr->vprint calls the parent version if passed a parent*, but child version if passed a child*
void PrintOnP(parent *ptr) {
   if (!ptr) return;
   ptr->vprint();
   ptr->sprint();
}

// can only be passed a pointer to a child (can't be passed a pointer to a parent)
//    calls the child version of vprint since it's being passed a child*
//    calls the child version of sprint since ptr is of type child*
void PrintOnC(child *ptr) {
   if (!ptr) return;
   ptr->vprint();
   ptr->sprint();
}

// resulting output shown in comments
int main()
{
   parent P(10);
   // P's one constructor runs
// Parent constructor 10
   child C(5);
   // C's two constructors run, parent then child
// Parent constructor 0
// Child constructor 0,5

   cout << "\nPrintOnP on parent 10" << endl;
   PrintOnP(&P); // vprint correctly identifies specific version to use (passed P is a parent)
                 // sprint just uses the one matching the ptr parameter type (parent)
// Parent vprint: P = 10
// Parent sprint: P = 10
   cout << "\nPrintOnP on child 5" << endl;
   PrintOnP(&C); // vprint correctly identifies specific version to use (passed C is a child)
                 // static just uses the one matching the ptr parameter type (parent)
// Child vprint: P = 0,5
// Parent sprint: P = 0

   cout << "\nPrintOnC on parent 10 is not valid, cannot pass parent as a child"  << endl;
   // PrintOnC(&P);
   cout << "\nPrintOnC on child 5" << endl;
   PrintOnC(&C); // vprint correctly identifies specific version to use (passed C is a child)
                 // sprint just uses the one matching the ptr parameter type (child)
// Child vprint: P = 0,5
// Child sprint: P = 0,5

   // C's two destructors run in order child then parent, then P's one destructor

// Child destructor on 0,5
// Parent destructor on 0

// Parent destructor on 10
}



