I'm new to C++, so I'm trying to learn the language by implementing all the type systems and languages in Pierce's Types and Programming Languages. My first attempt starts at the very beginning and implementes this very simple language Arithmetic Expressions on page 41 in the book (for those that want to look at it, which is not required to understand my question).I'm looking for at least advice on the following:Language feature use.Efficiency, e.g. unnecessary object creation.One point that bugs me is also that the language has the following evaluation rule. If nv is a numeric value, then pred (succ nv) evaluates to nv. To evaluate a pred term it seems like one needs to be able to peek inside the succ term to pull out the subterm. I'm currently doing this by making Pred a friend of the Succ class. However, such an dependency seems somewhat ugly to me.enum class TermType { kTrue, kFalse, kCond, kZero, kSucc, kPred, kIsZero };class Term { public:  virtual void print(std::ostream&) const = 0;  virtual std::shared_ptr<Term> eval() = 0;  virtual TermType GetType() const = 0;  virtual bool operator==(bool) const { return false; }  virtual bool operator==(int) const { return false; }};// Booleansclass True : public Term { public:  void print(std::ostream& out) const { out << True; }  std::shared_ptr<Term> eval() { return std::shared_ptr<Term>(new True()); }  TermType GetType() const { return TermType::kTrue; }  bool operator==(bool) const;};bool True::operator==(bool b) const { return b; }class False : public Term { public:  void print(std::ostream& out) const { out << False; }  std::shared_ptr<Term> eval() { return std::shared_ptr<Term>(new False()); }  TermType GetType() const { return TermType::kFalse; }  bool operator==(bool) const;};bool False::operator==(bool b) const { return !b; }// Conditionalsclass Conditional : public Term { public:  Conditional(Term* g, Term* t, Term* f)      : guard_{g}, tbranch_{t}, fbranch_{f} {}  Conditional(std::shared_ptr<Term>& g, std::shared_ptr<Term>& t,              std::shared_ptr<Term>& f)      : guard_{g}, tbranch_{t}, fbranch_{f} {}  void print(std::ostream&) const;  std::shared_ptr<Term> eval();  TermType GetType() const { return TermType::kCond; } private:  std::shared_ptr<Term> guard_;  std::shared_ptr<Term> tbranch_;  std::shared_ptr<Term> fbranch_;};void Conditional::print(std::ostream& out) const {  out << if (;  guard_->print(out);  out << ) {;  tbranch_->print(out);  out << } else {;  fbranch_->print(out);  out << };}std::shared_ptr<Term> Conditional::eval() {  if (*guard_->eval() == true)    return tbranch_->eval();  else    return fbranch_->eval();}// Numeralsclass Succ;class Zero : public Term { public:  void print(std::ostream& out) const { out << 0; }  std::shared_ptr<Term> eval() { return std::shared_ptr<Term>(this); }  TermType GetType() const { return TermType::kZero; }  bool operator==(int n) const { return n == 0; }};class Succ : public Term {  friend class Pred; public:  Succ(Term* t) : t_{t} {}  Succ(std::shared_ptr<Term> t) : t_{t} {}  void print(std::ostream&) const;  std::shared_ptr<Term> eval();  TermType GetType() const { return TermType::kSucc; } private:  std::shared_ptr<Term> t_;};void Succ::print(std::ostream& out) const {  out << succ ;  t_->print(out);}std::shared_ptr<Term> Succ::eval() {  return std::shared_ptr<Term>(new Succ(t_->eval()));}class Pred : public Term { public:  Pred(Term* t) : t_{t} {}  Pred(std::shared_ptr<Term> t) : t_{t} {}  void print(std::ostream&) const;  std::shared_ptr<Term> eval();  TermType GetType() const { return TermType::kPred; } private:  std::shared_ptr<Term> t_;};void Pred::print(std::ostream& out) const {  out << pred ;  t_->print(out);}std::shared_ptr<Term> Pred::eval() {  std::shared_ptr<Term> t = t_->eval();  if (t->GetType() == TermType::kSucc)    return std::static_pointer_cast<Succ>(t)->t_;  else    return t;}class IsZero : public Term { public:  IsZero(Term* t) : t_{t} {}  IsZero(std::shared_ptr<Term> t) : t_{t} {}  void print(std::ostream&) const;  std::shared_ptr<Term> eval();  TermType GetType() const { return TermType::kIsZero; } private:  std::shared_ptr<Term> t_;};void IsZero::print(std::ostream& out) const {  out << iszero ;  t_->print(out);}std::shared_ptr<Term> IsZero::eval() {  if (t_->eval() == 0)    return std::shared_ptr<Term>(new True());  else    return std::shared_ptr<Term>(new False());}I also don't really like the external TermType class. My design is pretty much based on what I would typically do in C, i.e. the AST is linked together using void pointers and the first field of each AST node type contains a numeric value, which tells the type of struct it needs to be cast to. Maybe there's a technique in C++ that allows getting around the sort of downcasting without incurring a runtime overhead?
AST implementation
c++;c++11;reinventing the wheel;grammar