Saturday 14 July 2018

Curiously Recurring Template Pattern (CRTP)

As a C++ developer we all are very comfortable with shape hierarchy example that we all study while learning virtual functions and run-time polymorphism and this post will just be leveraging that example to explain CRTP. So first things first "What is CRTP ?" .

CRTP or Curiously Recurring Template Pattern is C++ principle according to which Derived Class will inherit from template class and template parameter in that class will be derived class(curiously recurring !).

So with this definition in mind we will see how we can apply this principle to our classic Shape Class hierarchy in which all our derived classes namely Circle,Square and Rectangle will inherit from templated Shape class with respective derived class as template parameter. It seems everything is quite confusing till now so let's go look at below code snippet which should clear all our doubts :

#include <iostream>
class Rectangle;
class Circle;
class Square;
template<typename T>
class Shape
{
 public:
  Shape() = default;
  template<typename... Args>
  Shape(Args... args):ptr(new T(args...)){};
  double area()
  {
     return ptr->area();
  }
 protected:
  T* ptr;
 
};

class Rectangle:public Shape<Rectangle>
{
 public:
  Rectangle(unsigned int arg_nLength,unsigned int arg_nBreadth):m_nLength(arg_nLength),m_nBreadth(arg_nBreadth)
  {
  }
  double area()
  {
   return m_nLength*m_nBreadth;
  }
 private:
  unsigned int m_nLength;
  unsigned int m_nBreadth;
};

class Square:public Shape<Square>
{
 public:
  Square(unsigned int arg_nside):m_nside(arg_nside)
  {
  }
  double area()
  {
   return m_nside*m_nside;
  }
 private:
  unsigned int m_nside;
};
class Circle:public Shape<Circle>
{
 public:
  Circle(unsigned int r):R(r)
  {
  }
  double area()
  {
   return 3.14*R*R;
  }
 private:
  unsigned int R;

};
int main()
{
 Shape<Circle> C(3);
 Shape<Square> S(3);
 Shape<Rectangle> R(3,4);
 std::cout<<"Area of circle is "<<C.area()<<std::endl;
 std::cout<<"Area of square is "<<S.area()<<std::endl;
 std::cout<<"Area of square is "<<R.area()<<std::endl;
 return 0;
}
So with this example CRTP should be more clearer and it is also understood now that CRTP cannot replace virtual mechanism completely as we cannot have single pointer that can point to all the classes Shape<Circle>, Shape<Square> and Shape<Rectangle> as each is distince type in itself, but it seems we can get closer if our main idea is to have consistent interface for distinct types but given that type is known at compile time.Below is sample code with updated main for this approach :

template<typename T>
double calculateArea(Shape<T>& obj)
{
    return obj.area();
}
int main()
{
 Shape<Circle> C(3);
 Shape<Square> S(3);
 Shape<Rectangle> R(3,4);
 std::cout<<"Area of circle is "<<calculateArea(C)<<std::endl;
 std::cout<<"Area of square is "<<calculateArea(S)<<std::endl;
 std::cout<<"Area of rectangle is "<<calculateArea(R)<<std::endl;
 return 0;
}

No comments:

Post a Comment