Specialization without repeating all the implementation

Back to Deprecated techniques

This section was deprecated because C++11 offers better ways to deal with the problems in this section. We provide the original text here for historical reference.

The task of specializing a class template for certain kinds of template parameters is a frequently recurring one, be it partial or explicit specialization. A problem with specialization is that you need to respecify the code that is common to all instances of the class template in a specialization. The simplest approach is to use direct copy & paste in your text editor to give every specialization the common functionality. However, this leads to a maintenance problem since a change to the code requires to change the code in all replicates. In this article we present a technique that avoids this kind of code replication.

A warning in retrospect

This section has been written years later (18.06.2012) to report on the disadvantages of this technique. In short, I recommend against using the technique as specified in this page. I will give options for solving the problem in C++11 below.

Confusing code organization

The actual code in this technique goes into the CRTP base class. This is confusing in two ways. First, the actual code file, say array.h only contains classes to inherit from the CRTP base class, and therefore does not show any interface. This is confusing for documentation. Second, even if one understands to look at the CRTP base class for documentation, one finds it sprinkled with mentions to the Derived class.

Maintenance problems

The specializations create a maintenance problem in that all constructors, assignments etc. must be replicated across all versions. This quickly becomes a serious problem, although it might not seem like it at first.

Solving the specialization problem in C++11

Most of my use of this specialization technique has been to make generic multidimensional interfaces easier to use in fixed low dimensions. For example, to set the size of a 2-d array, I would have a specialization setExtent(width, height) rather than setExtent(Vector2i(width, height)). In retrospect, and in the light of C++11, these techniques have only been trying to simulate the effect of initialization lists. Once initialization list support becomes available, I will abandon the uses of CRTP specialization and use initialization lists instead.

The extraction of the x-component from an n-dimensional vector v is a bit different. Here we would like to write, in low-dimensions, v.x() instead of v[0]. In this case initialization lists do not help. An option in this case would be to make x(), y(), and z() part of the generic interface and then assert on the dimensionality at runtime. But the delay in error-reporting from compile-time to run-time would be unacceptable. In C++11 it is possible to remove these functions conditionally based on SFINAE using function template default parameters.

Setting up the scene

Assume we have implemented a mathematical vector class template which models vectors in ( for the set of real numbers) and which is templated with respect to dimension and its number type:

template <typename Real, integer N>
class Vector
{
public:
    // ...
    // Common functionality:

    explicit Vector(const Real& that)
    {
        // Set all elements to 'that'.
    }

    Real& operator[](integer index)
    {
        return data_[index];
    }

    Vector<Real, N>& operator+=(const Vector& that)
    {
        // Add stuff together..
        return *this;
    }

    Vector<Real, N> operator+(Vector that) const
    {
        return that += *this;
    }

    // ...

private:
    Real data_[N];
};

The class template works as follows:

#include "vector.h"
int main()
{
    Vector<3, float> v(0);

    v[0] = 1;
    v[1] = 0;
    v[2] = 0;

    Vector<3, float> a(v + v);
    a += v;

    return 0;
}

The template implementation assumes that the type Real models a certain concept which defines what you can do with Reals.

Problem statement

What we would like to do is to specialize Vector for dimension 3 in such a way that it works exactly like this general version except that it has additional member functions x(), y(), and z() to access the first, second, and third elements of the vector, respectively. Then we could rewrite the program above as:

#include "vector.h"

int main()
{
    Vector<3, float> v;

    v.x() = 1;
    v.y() = 0;
    v.z() = 0;

    Vector<3, float> a(v + v);
    a += v;

    return 0;
}

Which seems much clearer in this specialized case.

Copy and paste

The simplest approach is to use copy & paste. Here we replicate the common implementation code to each specialization:

// The general version

template <typename Real, integer N>
class Vector
{
public:
    // ...
    // Common functionality:

    explicit Vector(const Real& that)
    {
        // Set all elements to 'that'.
    }

    Real& operator[](integer index)
    {
        return data_[index];
    }

    Vector<Real, N>& operator+=(const Vector& that)
    {
        // Add stuff together..
        return *this;
    }

    Vector<Real, N> operator+(Vector that) const
    {
        return that += *this;
    }

private:
    Real data_[N];
};

// The three-dimensional version,
// partial specialization.

template <typename Real>
class Vector<3, Real>
{
public:
    // Specific functionality:

    Real& x()
    {
        return (*this)[0];
    }

    Real& y();
    {
        return (*this)[1];
    }

    Real& z();
    {
        return (*this)[2];
    }

    // ...
    // Common functionality:

    explicit Vector(const Real& that)
    {
        // Set all elements to 'that'.
    }

    Real& operator[](integer index);
    {
        return data_[index];
    }

    Vector<3, Real>& operator+=(const Vector& that);
    {
        // Add stuff together..
        return *this;
    }

    Vector<3, Real> operator+(Vector that) const
    {
        return that += *this;
    }

    // ...

private:
    Real data_[3];
};

The problem with this approach is that specializing n versions of the class template means to replicate the implementation n times. This makes code maintenance a nightmare.

Curiously Recurring Template Pattern

We shall now introduce an important template technique called the Curiously Recurring Template Pattern (CRTP). Here is the technique in short:

template <typename Real, integer N, typename Derived>
class VectorBase
{
    // ...
};

template <typename Real, integer N>
class Vector
: public VectorBase<Real, N, Vector<Real, N> >
{
    // ...
};

In words, the class Vector<Real, N> is derived from the class VectorBase<Real, N, Vector<Real, N> >. An important thing to notice here is that when Vector<Real, N> defines its base class, the Vector<Real, N> has only been declared but not defined. Thus, what VectorBase<Real, N, Derived> class definition sees is effectively:

class Derived;

But this is less restrictive than one first thinks. Let’s see what you can’t do. You can’t place Derived as a member variable of VectorBase. But even if you could that wouldn’t make sense since it would lead to an infinite recursion (Derived contains VectorBase contains Derived contains…). You also can’t derive VectorBase from Derived. But even if you could that wouldn’t make sense either for the same reason. Finally, you can’t refer to the types or constants of Derived from the declaration of the VectorBase (a declaration does not include function definitions/implementations). But this you can get around by passing them as template parameters to VectorBase.

However, in the definition of a function of the VectorBase the Derived class is fully defined. This is because the member functions of a class template are only instantiated when used. On the other hand, when the member functions are used, Derived must necessarily be already defined. Thus in the implementation we can use Derived as a fully defined class. After all, we are not restricted at all!

Using CRTP to avoid code replication

We shall now use CRTP to avoid code replication with specialization. Our strategy is to pack all the common functionality into the base class and then use inheritance to inject this code to the derived class. Nothing new? Well, the exciting thing here is that the base class now knows the name of its derived class and thus it can use correct parameter and return types.

Here’s how we achieve that:

// Common code

template <typename Real, integer N, Derived>
class VectorBase
{
public:
    // ...
    // Common functionality:

    explicit VectorBase(const Real& that)
    {
        // Set all elements to 'that'.
    }

    Real& operator[](integer index)
    {
        return data_[index];
    }

    Derived& operator+=(const Derived& that)
    {
        // Add stuff together..
        return (Derived&)*this;
    }

    Derived operator+(Derived that) const
    {
        // Note we are using here the copy constructor
        // of Derived.
        return that += *this;
    }

    // ...

private:
    Real data_[N];
};

// The general version.

template <typename Real, integer N>
class Vector
: public VectorBase<Real, N, Vector<Real, N> >
{
private:
    using Base = VectorBase<Real, N, Vector<Real, N> >;

public:
    explicit Vector(const Real& that)
    : Base(that)
    {
    }
};

// The three-dimensional version,
// partial specialization.

template <typename Real>
class Vector<3, Real>
: public VectorBase<3, Real, Vector<3, Real> >
{
private:
    using Base = VectorBase<3, Real, Vector<3, Real> >;

public:
    using Base::operator[];

    explicit Vector(const Real& that)
    : Base(that)
    {
    }

    // Specific functionality:

    Real& x()
    {
        return (*this)[0];
    }

    Real& y();
    {
        return (*this)[1];
    }

    Real& z();
    {
        return (*this)[2];
    }
};

That is, we use inheritance to avoid code replication. All the common functionality is inherited from VectorBase and now we only need to add the dimension specific functions in Vector.

Note that we must write using Base::operator[];. This is because if the base class is dependent on template parameters, then its contents are hidden to the derived class’s implementation. Note however that outside the implementation you can see the base class’s functions through the derived class as is the case normally. Thus the operator+= works although we do not declare using Base::operator+=;. Constructors are special in that they must be implemented to forward to their corresponding base class constructors. This inconvenience is remedied by the next C++ standard C++0x which allows to inher constructors.

Conclusion

We presented a common template trick to avoid code replication when specializing a class template.