9.8. The Curiously Recurring Template PatternThe pattern named in this section's title was first identified by James Coplien [Cop96] as "curiously recurring" because it seems to arise so often. Without further ado, here it is.
Because of the way X is derived from a class that "knows about" X itself, the pattern is sometimes also called "curiously recursive." CRTP is powerful because of the way template instantiation works: Although declarations in the base class template are instantiated when the derived class is declared (or instantiated, if it too is templated), the bodies of member functions of the base class template are only instantiated after the entire declaration of the derived class is known to the compiler. As a result, these member functions can use details of the derived class. 9.8.1. Generating FunctionsThe following example shows how CRTP can be used to generate an operator> for any class that supports prefix operator<: #include <cassert> template <class T> struct ordered { bool operator>(T const& rhs) const { // locate full derived object T const& self = static_cast<T const&>(*this); return rhs < self; } }; class Int : public ordered<Int> { public: explicit Int(int x) : value(x) {} bool operator<(Int const& rhs) const { return this->value < rhs.value; } int value; }; int main() { assert(Int(4) < Int(6)); assert(Int(9) > Int(6)); } The technique of using a static_cast with CRTP to reach the derived object is sometimes called the "Barton and Nackman trick" because it first appeared in John Barton and Lee Nackman's Scientific and Engineering C++ [BN94]. Though written in 1994, Barton and Nackman's book pioneered generic programming and metaprogramming techniques that are still considered advanced today. We highly recommend this book.
Another variation of the trick can be used to define non-member friend functions in the namespace of the base class:
namespace crtp
{
template <class T>
struct signed_number
{
friend T abs(T x)
{
return x < 0 ? -x : x;
}
};
}
If signed_number<T> is used as a base class for any class supporting unary negation and comparison with 0, it automatically acquires a non-member abs function:
class Float : crtp::signed_number<Float>
{
public:
Float(float x)
: value(x)
{}
Float operator-() const
{
return Float(-value);
}
bool operator<(float x) const
{
return value < x;
}
float value;
};
Float const minus_pi = -3.14159265;
Float const pi = abs(minus_pi);
Here the abs function is found in namespace crtp by argument-dependent lookup (ADL). Only unqualified calls are subject to ADL, which searches the namespaces of function arguments and their bases for viable overloads. It's a curious property of friend functions defined in the body of a class template that, unless also declared outside the body, they can only be found via ADL. Explicit qualification doesn't work: Float const erroneous = crtp::abs(pi); // error Keep that limitation in mind when generating free functions with CRTP. 9.8.2. Managing Overload ResolutionIn its simplest form, CRTP is used to establish an inheritance relationship among otherwise unrelated classes for the purpose of overload resolution, and to avoid overly general function template arguments. For example, if we are writing a generic function drive, which operates on Vehicles (where Vehicle is a Concept), we could write: template <class Vehicle> void drive(Vehicle const& v) { ... } This definition is perfectly fine until someone writes a generic function called "drive" that operates on Screws: template <class Screw> void drive(Screw const& s) { ... } The problem is that while the identifiers Vehicle and Screw have meaning to us, they are equivalent as far as the compiler is concerned. If the two drives are in the same namespace, both declarations refer to the same entity. If both function bodies are visible, we'll get a compilation error, but if only one body is visible, we'll have quietly violated the standard's "One Definition Rule," leading to undefined behavior. Even if they're not in the same namespace, unqualified calls to drive may be ambiguous, or worse, may end up invoking the wrong function. Because of the way that ADL quietly adds distant functions to the overload set, and because unqualified function calls are so natural, writing completely general function templates with parameters that can match all types is extremely dangerous. Consider the following contrived example: #include <list> namespace utility { // fill the range with zeroes template <class Iterator> Iterator clear(Iterator const& start, Iterator const& finish); // perform some transformation on the sequence template <class Iterator> int munge(Iterator start, Iterator finish) { // ... start = clear(start, finish); // ... } } namespace paint { template <class Canvas, class Color> // generalized template void clear(Canvas&, Color const&); struct some_canvas { }; struct black { }; std::list<some_canvas> canvases(10); int x = utility::munge(canvases.begin(), canvases.end()); } In fact, the instantiation of munge usually won't compile, because the list iterators will be class templates parameterized on paint::some_canvas. Argument-dependent lookup sees that parameter and finds a definition of clear in namespace paint, which is added to the overload set. Inside munge, paint::clear happens to be a slightly better match than utility::clear for the arguments passed. Fortunately for us, paint::clear returns void, so the assignment failsbut just imagine that clear returned a Canvas&. In that case, the code might have compiled "cleanly," but it would have silently done something completely unintended. To solve this problem, we can use the curiously recurring template pattern to identify models of our Vehicle and Screw concepts. We only need to add the requirement that models of each concept be publicly derived from a corresponding CRTP base class: template <class Derived> struct vehicle {}; template <class Derived> struct screw {}; Now our drive function templates can be rewritten to be more discriminating. The usual downcasts apply: template <class Vehicle> void drive(vehicle<Vehicle> const& v) { Vehicle const& v_= static_cast<Vehicle const&>(v); ... }; template <class Screw> void drive(screw<Screw> const& s) { Screw const& s_= static_cast<Screw const&>(s); ... }; ![]() |