7.5 Specialization

A template declares a set of functions or classes from a single declaration, which is a powerful tool for writing code once and having it work in a multitude of situations. A single, one-size-fits-all approach is not always appropriate, however. You can therefore specialize a template for specific values of one or more template arguments and provide a completely different definition for that special case.

You can specialize a class template, a function template, or a member of a class template. Declare a specialization with an empty set of template parameters in the template header followed by a declaration that provides arguments for all the template parameters:

template<> declaration

Specializations must appear after the declaration of the primary template, and they must be declared in the same namespace as the primary template. You can also have a partial specialization, which provides some of the template arguments. (See the next section, Section 7.6.)

When a template is instantiated, if the template arguments of the instantiation match the arguments used in a specialization, the compiler instantiates the matching specialization. Otherwise, it instantiates the primary template. See Section 7.7 later in this chapter for details.

To specialize a function template, the function return type and parameter types must match the primary template declaration. The function name can be followed by the specialized template arguments (enclosed in angle brackets and separated by commas), or you can omit them and let the compiler deduce the specialized arguments. See Section 7.3.2 earlier in this chapter for details.

If you need to change the function parameters, you can overload the function, but that requires an entirely new function or function template declaration, not a specialization. Example 7-7 shows two specializations of a function template.

Example 7-7. Specializing a function template
// Primary template declaration

template<typename T> T root(T x, T y)

{

  return pow(x, 1.0/y);

}



// Specialization in which T is deduced to be long

template<> long root(long x, long y)

{

  if (y == 2)

    return sqrt((long double)x);

  else

    return pow((long double)x, 1.0l/y);

}



// Specialization for explicit T=int

template<> int root<int>(int x, int y)

{

  if (y == 2)

    return sqrt(double(x));

  else

    return pow(double(x), 1.0/y);

}



// Overload with a different function template, such as valarray<T>.

template<template<typename T> class C, typename T>

C<T> root(const C<T>& x, const C<T>& y)

{

  return pow(x, C<T>(1.0)/y);

}

A specialization of a class template requires an entirely new class declaration or definition. The members of the specialized class template can be entirely different from the members of the primary class template. If you want to specialize only some members, you can specialize just those members instead of specializing the entire class.

In a specialization of a static data member, you must supply an explicit initializer to define the data member. Without an initializer, the member template declaration is just a declaration of the data member, not a definition. If you want to use the static data member, you must make sure it is defined:

template<typename T>

struct demo {

  static T data;

};

template<> int demo<int>::data;      // Declaration

template<> int demo<int>::data = 42; // Definition

Declare specializations before the template is used. (See Section 7.7 later in this chapter for information on how templates are "used.") A program must have a single specialization for a given set of template arguments.

Example 7-8 shows some specializations of a class template, type_traits, which exposes a few attributes, or traits, of a type. The primary template describes the default traits, and the template is specialized for specific types, such as int. See Chapter 8 for more information about traits.

Example 7-8. Specializing a class template
#include <iostream>

#include <ostream>

#include <sstream>

#include <string>

#include <typeinfo>



template<typename T>

struct type_traits

{

  typedef T base_type;

  enum { is_fundamental = 0 };

  enum { is_integer = 0 };

  enum { is_float = 0 };

  enum { is_pointer = 0 };

  enum { is_reference = 0 };

  static std::string to_string(const T&);

};



template<typename T>

std::string type_traits<T>::to_string(const T& x)

{

  return typeid(x).name(  );

}



// Specialize entire class for each fundamental type.

template<>

struct type_traits<int>

{

  typedef int base_type;

  enum { is_fundamental = 1 };

  enum { is_integer = 1 };

  enum { is_float = 0 };

  enum { is_pointer = 0 };

  enum { is_reference = 0 };

  static std::string to_string(const int& x) {

    std::ostringstream out;

    out << x;

    return out.str(  );

  }

};



struct point

{

  int x, y;

};



// Specialize only the to_string(  ) member function for type point.

template<>

std::string type_traits<point>::to_string(const point& p)

{

  std::ostringstream out;

  out << '(' << p.x << ',' << p.y << ')';

  return out.str(  );

}



int main(  )

{

  using std::cout;

  cout << type_traits<point>::is_fundamental << '\n';

  cout << type_traits<point>::is_pointer << '\n';

  cout << type_traits<point>::to_string(point(  )) << '\n';

  cout << type_traits<int>::is_fundamental << '\n';

  cout << type_traits<int>::is_pointer << '\n';

  cout << type_traits<int>::is_integer << '\n';

  cout << type_traits<int>::to_string(42) << '\n';

}