Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / Visual C++ 10.0
Tip/Trick

C++: Prefer Curiously Recurring Template Pattern (CRTP) to Template Pattern

Rate me:
Please Sign up or sign in to vote.
4.84/5 (13 votes)
26 Oct 2016CPOL2 min read 54.4K   329   14   4
C++: Prefer Curiously Recurring Template Pattern (CRTP) to Template Pattern

Introduction

The Template Pattern defined in the GoF Design Patterns book, is unrelated to C++ templates and is a behavioral pattern. The Curiously Recurring Template Pattern (CRTP) is an improvement on the Template Pattern and is a C++ idiom in which a class X derives from a class template instantiation using X itself as template argument. The name of this idiom was coined by Jim Coplien, who had observed it in some of the earliest C++ template code. This technique achieves a similar effect to the use of virtual functions, without the costs (and some flexibility) of dynamic polymorphism. CRTP can be used in place of Template Pattern, provided that dynamic polymorphism is not required at runtime. This pattern is used extensively in the Windows ATL and WTL libraries.

Template Pattern

Let us look at the vanilla Template Pattern first. The Template Pattern makes use of polymorphism and a template method to do its work. In our example, the abstract base class, AbstractTextout, has 11 overloaded Print functions and a pure virtual function, Process which is only implemented in the derived class. Examples of possible useful derived class could be logging, console output and debug printing. Only debug printing class is implemented.

C++
class AbstractTextout
{
public:
    void Print(const wchar_t* fmt);
    // ... plus 10 other overloaded Print with different number of Box arguments
protected:
    virtual void Process(const std::wstring& str) = 0;
};

The code for one of the Print functions is shown below. Box argument is responsible for converting the POD to string. The difference with the rest of the overloaded Print functions are just pushing back more Box arguments into the vs. I would not cover the Box class as it is not the focus of this tip/trick. Readers may download the source code from the tip.

C++
void AbstractTextout::Print(const wchar_t* fmt, Box D1)
{
    std::wstring wsfmtstr = fmt;

    std::vector<std::wstring> vs;
    vs.push_back( D1.ToString() );

    std::wstring str = StrReplace( wsfmtstr, vs );

    Process(str); // implemented in the derived class only.
}

This is how the derived class, DebugPrint, implemented the Process function in DebugPrint.cpp.

C++
void Process(const std::wstring& str)
{
#ifdef _DEBUG
    OutputDebugStringW( str.c_str() );
#endif
}

This is how DebugPrint class is used.

C++
#include "DebugPrint.h"

void main()
{
    DebugPrint dp;

    dp.Print(L"Product:{0}, Qty:{1}, Price is ${2}\n", L"Shampoo", 1200, 2.65);
    // display "Product:Shampoo, Qty:1200, Price is $2.650000"
}

A vtbl is implemented for class with virtual functions. There is an overhead of the vtbl to determine the correct function to call. Curiously Recurring Template Pattern is using static polymorphism to eliminate this overhead. Let us see how it is achieved in the following section.

Curiously Recurring Template Pattern

AbstractTextout is now a template class which means all the code defined in the cpp has to move to the header file. Before calling Process, the code will cast itself to the derived type first.

C++
template <typename Derived> 
class AbstractTextout
{
public:
    void Print(const wchar_t* fmt, Box D1 )
    {
        std::wstring wsfmtstr = fmt;

        std::vector<std::wstring> vs;
        vs.push_back( D1.ToString() );

        std::wstring str = StrReplace( wsfmtstr, vs );

        static_cast<Derived*>(this)->Process(str);
    }
}

DebugPrint is unchanged, except itself is a template type in its base class, AbstractTextout and I have to make Process non-virtual and move it to header file.

C++
class DebugPrint : public AbstractTextout<DebugPrint>
{
public:
    void Process(const std::wstring& str)
    {
#ifdef _DEBUG
        OutputDebugStringW( str.c_str() );
#endif
    }
};

Usage of DebugPrint class remains unchanged.

C++
#include "DebugPrint.h"

void main()
{
    DebugPrint dp;

    dp.Print(L"Product:{0}, Qty:{1}, Price is ${2}\n", L"Shampoo", 1200, 2.65);
    // display "Product:Shampoo, Qty:1200, Price is $2.650000"
}

Related Links

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Singapore Singapore
Shao Voon is from Singapore. His interest lies primarily in computer graphics, software optimization, concurrency, security, and Agile methodologies.

In recent years, he shifted focus to software safety research. His hobby is writing a free C++ DirectX photo slideshow application which can be viewed here.

Comments and Discussions

 
GeneralIn non-performance-critical code (like logging) using CRTP is nothing but code bloat. Pin
Sharif Ahmad15-Nov-16 7:28
professionalSharif Ahmad15-Nov-16 7:28 
QuestionInjection Pin
bling30-Oct-16 8:03
bling30-Oct-16 8:03 
QuestionSelf documented code Pin
Yakigato Niji1-Feb-13 5:46
Yakigato Niji1-Feb-13 5:46 
I have been using this pattern for a long time and, if there is no need to polymorphism, it can save you some performance headaches in thigh systems. There is only one issue with this pattern that bothers me and I still fail to find a good way to fix it. Declaring an abstract method does not only ask the compiler to handle a vtable. It also mean something to the developers who will use that class. It tells them that there is a behavior that need to be implemented explicitly in their derived types. It also tells them what this behavior is through the method name and parameters.

The problem with the CRTP is that there is no way (that I have found yet) to properly document this behavior in the template base class. A user of this class have to read the entire implementation to properly understand what needs to me implemented in the derived types. That is, assuming that there is no extra documentation. Have you thought about this problem, and if so, have you found a way to illustrate this concept clearly?

Aside from always providing a dummy default implementation, nothing comes in my mind...
GeneralMy vote of 5 Pin
roxma31-Jan-13 19:46
roxma31-Jan-13 19:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.