C++: CRTP: The curiously recurring template pattern

This class (for example):

class Writer
{
  public:
    Writer() = default;
    virtual ~Writer() = default;

    virtual void write(std::string_view aStr) const = 0;
};

can be useful as the base class for implementing several specialized writers (for example, a file writer and a console writer).:

class FileWriter final : public Writer
{
  public:
    explicit FileWriter(FILE* aFile) { mFile = aFile; }
    ~FileWriter() override { if (mFile) fclose(mFile); }

    void write(std::string_view aStr) const override
    {
      if (mFile) fprintf(mFile, "%s\n", aStr);
    }

  private:
    FILE* mFile;
};

class ConsoleWriter final : public Writer
{
  public:
    ConsoleWriter() = default;
    ~ConsoleWriter() override = default;

    void write(std::string_view aStr) const override
    {
      fmt::print(aStr);
    }
};

Nice, isn’t it? The Writer::write() method can be used to write to any kind of media depending on the subclass that is implemented. So, what is the problem here? PERFORMANCE: At runtime, the application must determine which implementation of the method to execute, which can take some time. Although the performance loss is likely negligible in most cases, it is not zero and should be considered.

The “curiously recurring template pattern” (CRTP) is a very elegant way to implement something called “static polymorphism.” By using “static polymorphism,” the application will not need to decide which method implementation will be executed, as that decision will be made by the compiler.

The concept of the CRTP idiom can be outlined as follows:

  1. A template class Base is created to be used as a base class.
  2. The method is implemented that will call the desired method implemented in the child class.
  3. A subclass Child : Base is created (T = Child… that is why this is called “recurring”).
  4. The desired method is implemented in the child class.

Thus, the example can be converted to:

template <typename T>
class Writer
{
  public:
    Writer() = default;
    ~Writer() = default;

    void write(std::string_view str) const
    {
      static_cast<const T*>(this)->writeImpl(str); // here the magic is!!!
    }
};

class FileWriter final : public Writer<FileWriter> // note the recursive pattern here
{
  public:
    explicit FileWriter(FILE* aFile) { mFile = aFile; }
    ~FileWriter() { if (mFile) fclose(mFile); }

    // here comes the implementation of the write method on the subclass
    // notice this method is not virtual!
    void writeImpl(std::string_view str) const
    {
      if (mFile) fprintf(mFile, "%s\n", str.data());
    }

  private:
    FILE* mFile;
};

class ConsoleWriter final : public Writer<ConsoleWriter>
{
  public:
    ConsoleWriter() = default;
    ~ConsoleWriter() = default;

    void writeImpl(std::string_view str) const
    {
      fmt::print(str);
    }
};

This can be used as follows:

int main()
{
  FILE* file = fopen("file.txt", "w");
  FileWriter writer{file};
  writer.write("Hello world");
  return 0;
}

As can be seen in this last example, the Writer::write() method behaves like the virtual method defined in the first example, but the binding is performed at compile time. Therefore, the performance of calling a method using this pattern is better than implementing everything in the “normal” way.

Obviously, the flexibility of dynamic polymorphism is not available, but this idiom can be very useful for creating classes with basic functionality, developing subclasses with more specific functionality, and using them without incurring any performance cost.

6 thoughts on “C++: CRTP: The curiously recurring template pattern

  1. But there is a caveat: the called method must be PUBLIC, otherwise it won’t compile. If your class design had to call a protected/private method, you’ll have to mess the design to make the method public.

  2. I have read quite a few posts about CRTP recently, but they all seem to focus on the building the class hierarchy rather than using it. Could you come up with some more realistic example, that benefits from the CRTP? Cause your main function would work even if your concrete writers are not inherited from template class Writer. Here’s an example: http://ideone.com/BKK0Ff

    1. In my example, consider the fact you have a class that writes several things to some “lower level” entity (screen, disk, etc). The classes that have that low level knowledge, do not need to deal with all the stuff your “higher level” implementation needs. Remember, templates used in this way provide some traits to the general behavior of the class.

      See my example below, your base class published two extra methods “say_hi” and “say_bye” that should not need to be implemented by the specific writer.

      Do I answer your question? Thanks for reading me!

      #include

      template <class T>
      class Writer
      {
      public:
      Writer() { }
      ~Writer() { }

      void write(const char* str) const
      {
      static_cast<const T*>(this)->writeImpl(str);
      }

      void say_hi() const
      {
      write(“Hi”);
      }

      void say_bye() const
      {
      write(“Bye”);
      }
      };

      class ConsoleWriter : public Writer<ConsoleWriter>
      {
      public:
      ConsoleWriter() { }
      ~ConsoleWriter() { }

      void writeImpl(const char* str) const
      {
      printf(“%s\n”, str);
      }
      };

      class ConsoleWriterNoInheritance
      {
      public:
      ConsoleWriterNoInheritance() { }
      ~ConsoleWriterNoInheritance() { }

      void write(const char* str) const
      {
      printf(“%s\n”, str);
      }
      };

      int main()
      {
      ConsoleWriter writer;
      writer.write(“Hello world”);
      writer.say_hi();
      ConsoleWriterNoInheritance other_writer;
      other_writer.write(“Other”);
      //other_writer.hi(); DOES NOT COMPILE
      return 0;
      }

      1. Thanks, this snippet makes it clearer. This looks like strategy pattern, so I would prefer it to look more like this: http://ideone.com/ZYgwsA because it does not have this awkward inside-out structure, although it is probably a matter of taste and habit.

Leave a reply to lak Cancel reply