Partial specialization

Now, we are getting to the really interesting part of the C++ template programming—partial template specializations. When a class template is partially specialized, it remains as generic code, but less generic than the original template. The simplest form of the partial template is one where some of the generic types are replaced by concrete types, but other types remain generic:

template <typename N, typename D>
class Ratio {
.....
};

template <typename D>
class Ratio<double, D> {
public:
Ratio() : value_() {}
Ratio(const double& num, const D& denom) : value_(num/double(denom)) {}
explicit operator double() const { return value_; }
private:
double value_;
};

Here, we convert the Ratio to a double value if the numerator is double, regardless of the denominator type. More than one partial specialization can be defined for the same template. For example, we can also specialize for the case when the denominator is double and the numerator is anything:

template <typename N>
class Ratio<N, double> {
public:
Ratio() : value_() {}
Ratio(const N& num, const double& denom) : value_(double(num)/denom) {}
explicit operator double() const { return value_; }
private:
double value_;
};

When the template is instantiated, the best specialization for the given set of types is selected. In our case, if neither the numerator or the denominator is double, then the general template has to be instantiated—there are no other choices. If the numerator is double, then the first partial specialization is a better (more specific) match than the general template. If the denominator is double, then the second partial specialization is a better match. But what happens if both terms are double? In this case, the two partial specializations are equivalent; neither is more specific than the other. This situation is considered ambiguous and the instantiation fails. Note that only this particular instantiation, Ratio<double, double>, fails—it is not an error (at least, not a syntax error) to define both specializations, but it is an error to request an instantiation that cannot be uniquely resolved to the narrowest specialization. To allow any instantiation of our template, we have to remove this ambiguity, and the only way to do that is to provide an even more narrow specialization that would be preferred over the other two. In our case, there is only one option—a full specialization for Ratio<double, double>:

template <>
class Ratio<double, double> {
public:
Ratio() : value_() {}
template <typename N, typename D>
Ratio(const N& num, const D& denom) :
value_(double(num)/double(denom)) {}
explicit operator double() const { return value_; }
private:
double value_;
};

Now, the fact that the partial specializations are ambiguous for the instantiation of Ratio<double, double> is no longer relevant—we have a more specific version of the template than either of them, so that version is preferred over both. 

Partial specializations do not have to specify some of the generic types fully. Therefore, can keep all types generic, but impose some restrictions on them. For example, we still want a specialization where both the numerator and the denominator are pointers. They can be pointers to anything, so they are generic types, but less generic than the arbitrary types of the general template:

template <typename N, typename D>
class Ratio<N*, D*> {
public:
Ratio(N* num, D* denom) : num_(num), denom_(denom) {}
explicit operator double() const {
return double(*num_)/double(*denom_);
}
private:
N* const num_;
D* const denom_;
};
int i = 5; double x = 10;
auto r(make_ratio(&i, &x)); // Ratio<int*, double*>
double(r); // 0.5
x = 2.5;
double(r); // 2

This partial specialization still has two generic types, but they are both pointer types, N* and D*, for any N and D types. The implementation is totally unlike that of the general template. When instantiated with two pointer types, the partial specialization is more specific than the general template and is considered a better match. Note that, in our example, the denominator is double. So why isn't a partial specialization for the double denominator considered? That is because, while the denominator is double as far as the program logic is concerned, technically it is double*, a completely different type, and we do not have a specialization for that. 

To define a specialization, the general template must first be declared. It does not, however, need to be defined—it is possible to specialize a template that does not exist in the general case. To do so, we must forward-declare the general template, then define all the specializations we need:

 template <typename T> class Value; // Declaration
template <typename T> class Value<T*> {
public:
explicit Value(T* p) : v_(*p) {}
private:
T v_;
};
template <typename T> class Value<T&> {
public:
explicit Value(T& p) : v_(p) {}
private:
T v_;
};

int i = 5;
int* p = &i;
int& r = i;

Value<int*> v1(p); // T* specialization
Value<int&> v2(r); // T& specialization

Here, we have no general Value template, but we have them for any pointer or reference types. If we try to instantiate the template on some other type, like int, we will get an error stating that the Value<int> type is incomplete—this is no different than trying to define an object with only a forward declaration of the class.

So far, we have seen only the examples of partial specialization for class templates. Unlike the earlier discussion of the full specializations, we have not seen a single function specialization here. There is a very good reason for that—a partial function template specialization does not exist in C++. What is sometimes incorrectly called a partial specialization is nothing more than overloading template functions. On the other hand, overloading template functions can get quite complex and is worth learning about—we will cover this next.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.167.196