Specialization

Introduction

A template gives a single definition to be used for every template argument that a user can think of.
But sometimes, we want to provide alternative definitions of the template and have the compiler to choose between them based on the template arguments provided where they are used. This is called user-defined specializations or user specializations.

There are two types of specialization:

  • Complete specialization.
  • Partial specialization.

Complete Spectialization

Complete specialization means there is no template parameter to specify or deduce when we use the specialization. For example:

1
2
3
4
5
6
7
8
9
template<typename T1, typename T2>
class Primary {
T1 a;
T2 b;
};

template<>
class Primary<int, double> {
};

We use template<> where there is nothing in <> to specify that this is a complete specialization.

Partial Specialization

Partial Specialization means a specialization with a pattern containing a template parameter, in contrast to complete specialization, where the pattern is simply a specific type.
For example:

1
2
3
4
5
6
7
8
9
template<typename T1, typename T2>
class Primary {
T1 a;
T2 b;
};

template<typename T2>
class Primary<int, T2> {
};

Interface Specialization

Sometimes, a specialization is not an algorithmic optimization, but a modification of an interface.
For example, the standard library complex uses specializations to adjust constructors and some import operations:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename T>
class complex {
public:
complex(const T& re = T{}, const T& im = T{});
complex(const complex&); // copy constructor
template<typename X>
complex(const complex<X>&); // conversion from complex<X> to complex<T>

complex& operator=(const complex&);
complex<T>& operator=(const T&);
compltex<T>& operator += (const T&);

template<typename X>
complex<T>& operator=(const complex<X>&);
template<typename X>
compltex<T>& operator+=(const complex<X>&);
};

But it’s not efficient for floats:

1
2
3
4
5
6
7
template<>
class complex<float> {
public:
complex<float>& operator=(float);
complex<float>& operator+=(float);
complex<float>& operator=(const complex<float>&);
};

Implement Specialization

Specialization can be used to provide alternative implementations of a class template for a specific set of template parameters:

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename T, int N>
class Matrix;

template<typename T, 0>
class Matrix {
T val;
};

template <typename T, 1>
class Matrix {
T* elem;
int sz;
};

The Primary Template

When we have both a general definition of a template and specializations defining implementations for specific sets of template arguments, we refer to the most general template as the primary template.

Notice 1: The primary template must be declared before any specialization:

1
2
3
4
5
6
7
8
template<typename T>
class List<T*> {
//...
};

template<typename T>
class List {
}; // error!

Notice 2: For technically reasons, concepts need to be replicated in every specialization.

Notice 3: if primary template is never instantiated, it need not to be defined.

Notice 4: Specialization must be defined first when using it, can it should be in the scope for every use:

1
2
3
4
5
6
7
8
9
10
template<typename T>
class List {
//...
};

List<int*> li;

template<typename T>
class List<T*> {
};

Notice 5: it’s essential that every use of a template for a given set of template arguments be implemented by the same specialization.

Order of Specialization

The most specialized version will be preferred over the others in declarations of objects, pointers, etc.

More specialized means: for one specialization, if every argument list that matches its specialization pattern also matches the other, but vise versa.

Function Template Specialization

Specialization is also useful for template functions. However, we can overload functions. So there is less specialization.

Notice: C++ only supports complete specialization for functions.

Specialization and Overloading

let’s see the similarity between specialization and overloading.
Example: Shell sort.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<typename T>
bool less(T a, T b) {
return a < b;
}

template<typename T>
void sort(vector<T>& v) {
const size_t n = v.size();

for (int gap = n / 2; 0 < gap; gap /= 2) {
for (int i = gap; i != n; ++i) {
for (int j = i - gap; 0 <= j; j -= gap) {
if (less(v[j + gap], v[j])) {
swap(v[j], v[j + gap]);
}
}
}
}
}

If we pass Vector<char*>, it won’t work correctly because < will compare the address of pointers. So let’s overload less():

1
2
3
4
template<>
bool less<const char*>(const char* a, const char* b) {
return strcmp(a, b) < 0;
}

Template argument can be deduced from function argument list:

1
2
3
4
template<>
bool less<>(const char* a, const char* b) {
return strcmp(a, b) < 0;
}

Given the template<> prefix, the second empty<> is redundant:

1
2
3
4
template<>
bool less(const char* a, const char* b) {
return strcmp(a, b) < 0;
}

In this time, specialization and overloading has become razor thin and largely irrelevant:

1
2
3
bool less(const char* a, const char* b) {
return strcmp(a, b) < 0;
}

Specialization That Is Not Overloading

There are a few uses of function specializations. One example is functions taking no arguments:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<typename T>
T max_value(); // no definition

template<>
constexpr int max_value<int>() {
return INT_MAX;
}
template<>
constexpr char max_value<char>() {
return CHAR_MAX;
}

template<typename Iter>
Iter f(Iter p) {
auto x = max_value<Value_type<Iter>>(); // works for types with specialized max_value()
}

To get a roughly equivalent effect with overloading, we would have to pass a dummy(unused) arguments:

1
2
3
4
5
6
7
int max2(int) { return INT_MAX; }
char max2(char) { return CHAR_MAX; }

template<typename Iter>
Iter f2(Iter p) {
auto x = max2(Value_type<Iter>{});
}