Code Yarns ‍👨‍💻
Tech BlogPersonal Blog

A simple example to understand CRTP

📅 2015-Jul-09 ⬩ ✍️ Ashwin Nanjappa ⬩ 🏷️ cpp, crtp, dynamic polymorphism, static polymorphism, template ⬩ 📚 Archive

Dynamic polymorphism (or runtime polymorphism) using virtual functions in C++ is a great savior. However, a tiny fraction of applications might be affected by its disadvantages: an extra function call overhead and space occupied by a vtable pointer in every object.

For most of these applications, there might be no better choice than virtual functions, they will need to grin and bear it. But among these applications, there might be a further tiny fraction whose problem just might be elegantly solved using a static polymorphism technique called Curiously Recurring Template Pattern (CRTP). It is also called the Barton-Nackman trick, since this duo introduced it for the first time in their 1997 book Scientific and Engineering C++ (Section 12.6: Restricted Template Expansion).

The web is full of articles (like this) and code explaining CRTP. While I could understand how the code worked, none of them explained what problem this trick was solving. In other words, I did not see the motivation for using this trick.

It is only when I went back to the source, i.e., the Barton-Nackman book that I hit pay dirt. They actually describe a contrived problem which this trick solves. However, their code examples were still quite obscure, so to understand them I created a simpler example, which I describe below.

Problem

struct Creature {};
struct Bird: Creature {};
struct Fish: Creature {};
Bird b1, b2, b3;
// ...
if (b1 == b2) // Do something
if (b2 != b3) // Do something

Solutions

Let us see different ways this can be solved elegantly:

#include <iostream>
using namespace std;

template <typename Derived>
struct Creature
{
    int eye_num;

    friend bool operator == (const Derived& lhs, const Derived& rhs)
    {
        return lhs.IsEqual(rhs);
    }

    friend bool operator != (const Derived& lhs, const Derived& rhs)
    {
        return !(lhs.IsEqual(rhs));
    }
};

struct Bird: Creature<Bird>
{
    int wing_num;

    bool IsEqual(const Bird& b) const
    {
        return eye_num == b.eye_num && wing_num == b.wing_num;
    }
};

struct Fish: Creature<Fish>
{
    int fin_num;

    bool IsEqual(const Fish& f) const
    {
        return eye_num == f.eye_num && fin_num == f.fin_num;
    }
};

int main()
{
    Bird b1, b2, b3;

    b1.eye_num  = 2;
    b2.eye_num  = 2;
    b3.eye_num  = 2;

    b1.wing_num = 2;
    b2.wing_num = 2;
    b3.wing_num = 3;

    if (b1 != b2) cout << "Bird equal\n";
    else          cout << "Bird not equal\n";
    if (b1 == b3) cout << "Bird equal\n";
    else          cout << "Bird not equal\n";

    // You can do the same with Fish:
    // Fish f1, f2, f3;
    // f1.fin_num = 5;
    // and so on ...
}

I hope this example helped you understand this trick.


© 2023 Ashwin Nanjappa • All writing under CC BY-SA license • 🐘 Mastodon📧 Email