C++. Class templates. Full specialization. Partial specialization

Class templates. Full specialization. Partial specialization

Before studying this topic, it is recommended that you familiarize yourself with the following topic:

Contents


Search other websites:

1. The concept of full and partial specialization. General form
In the simplest case, the declaration of a template class that operates on the generic type T looks like this:
template <class T>
class ClassName
{
  // the body of the class
  // ...
}
here
  • ClassName – the name of the template class;
  • T is a generic type name that can be used in the ClassName class. This name may be different.
Based on the above pattern, it is possible to declare concrete instances of a class that are directly bound to some type.
ClassName<int> objInt;    // a class is formed for type int
ClassName<double> objDouble;  // a class is formed for type double
Instances of the objInt and objDouble classes are generated at compile time. This way of creating code is a special case of static polymorphism.
In the C++ language, it is allowed to declare the so-called “specialization” of a template class. Specialization allows you to edit the creation of an instance of a generic class for special cases. These special cases can be:
  • prohibition of using the class for some types. For example, if the class operates on numbers, then string types are inappropriate here (it is impossible to perform mathematical calculations on characters);
  • an implementation is provided for a pointer to a generic type. For example, for a generic type T, you need to implement a declaration of a pointer to this type (T*), taking into account the fact that the char* type can be a character string and is processed in a special way;
  • other cases.
Template class specialization can be of two types:
  • full specialization;
  • partial specialization.
A complete specialization has the following general form:
template <>
class ClassName<type>
{
  // class body
  // ...
}
here
  • ClassName is the name of the class for which the full specialization is being declared. Important: before declaring a full specialization, the class must be declared as a template class;
  • type is a type that defines the specialization of the template type declared in the ClassName class template. The type type can be any standard type (int, float, double, etc.) or a program-defined type.
Given the above general form, the declaration of an instance of a class under full specialization could be:
ClassName<type> obj;
here
  • obj – name of an instance (object) of the class;
  • type –  the type defined when declaring a full specialization of the class.
A partial specialization has the following general form:
template <class T>
class ClassName<specialization_T>
{
  // class body
  // ...
}
here
  • ClassName – the name of the template class for which the partial specialization is defined. Important: the ClassName class template must be declared in the program;
  • T – class type, which is defined in the template;
  • specialization_T – specialization of type T. This can be, for example, a pointer of type T, which is defined as T*.
Given the above general form of partial specialization, the declaration of a class object can be as follows:
ClassName<specialization_T> obj;
here
  • obj – the name of the class object;
  • specialization_T – specialization of type T (for example, T*, T**, etc.);
  • ClassName – the name of the template class.

 

2. Example of full and partial specialization implementation. Class Value<T>
The example demonstrates:
  • declaration of the template class Value<T>;
  • full specialization of the Value<T> class for the char* type;
  • full specialization of the Value<T> class for the void* type;
  • partial specialization of the Value<T> class for the type T* – a pointer to any type other than char*.
#include <iostream>
using namespace std;

// Template classes. Complete specialization. Partial specialization.

// 1. An ordinary template class declaration.
// Template class Value<T> is given, retaining some value.
template <class T>
class Value
{
private:
  T value;

public:
  // Constructor with 1 parameter
  Value(T value = 0) : value(value) { }

  // Testing method
  void Print(string msg)
  {
    cout << msg << " = " << value << endl;
  }

  // Method that returns a value
  T Get() { return value; }
};

// 2. Full specialization for type char*,
// a sign of full specialization is the string template<>
template <>
class Value<char*>
{
private:
  char* s;

  // Copying s <= _s
  void Copy(const char* _s)
  {
    int len = 0;
    while (_s[len++] != '\0');
    s = new char[len + 1];
    for (int i = 0; i <= len; i++)
      s[i] = _s[i];
  }

public:
  // Constructor
  Value(const char* _s = nullptr)
  {
    // Copying s <= _s
    if (_s != nullptr)
      Copy(_s);
  }

  // Testing method
  void Print(string msg)
  {
    cout << msg << " => " << s << endl;
  }

  // Destructor
  ~Value()
  {
    delete[] s;
  }

  // Copy constructor - delegates authority to a constructor with 1 parameter
  Value(const Value<char*>& obj) : Value<char*>(obj.s) { }

  // Copy operator
  Value& operator=(const Value<char*>& obj)
  {
    // 1. Free the memory allocated for the previous line
    delete[] s;

    // 2. Allocate memory for a new line, copy s<=obj.s
    Copy(obj.s);

    // 3. Return the current object
    return *this;
  }

  // The Get() method - returns a pointer to char
  char* Get() { return s; }
};

// 3. Full specialization for void*,
// there are no template parameters here <> as well
template <>
class Value<void*>
{
private:
  void* p;

public:
  Value() { p = nullptr; }
  Value(void* _p) : p(_p){ }

  // Метод Get()
  void* Get() { return p; }
};

// 4. Partial specialization,
// which is used for all pointers of type T
template <class T>
class Value<T*> // all void* pointers can be cast to any T*
{
private:
  T* p;

public:
  Value(T* p)
  {
    // make a copy
    this->p = new T;
    *(this->p) = *p;
  }

  void Print(string msg)
  {
    cout << msg << " = " << *p << endl;
  }

  // Copy constructor
  Value(const Value& obj) : Value(obj.p)
  {}

  // Copy operator
  Value& operator=(const Value& obj)
  {
    *p = *obj.p;
    return *this;
  }

  // Destructor
  ~Value()
  {
    delete p;
  }

  // The Get() method - does not return a copy
  T* Get() { return p; }
};

int main()
{
  // The test of Value<T> class.
  // 1. Regular template class declaration
  Value<int> vi1 = 22;
  vi1.Print("vi1");

  // 2. Full specialization for the char* type
  // 2.1. Declare an instance
  Value<char*> vc1 = "Hello, world!";
  vc1.Print("vc1");

  // 2.2. The copy constructor test
  Value<char*> vc2 = vc1;
  vc2.Print("vc2");

  // 2.3. Copy operator test
  Value<char*> vc3;
  vc3 = vc1;
  vc3.Print("vc3");

  // 2.4. Method Get()
  char* vc4 = vc2.Get();
  cout << "vc4 = " << vc4 << endl;

  // 3. Full specialization for void* type
  // Here the full specialization for void* is called
  Value<void*> p2;
  void* pvoid = p2.Get();

  // 4. Partial specialization for type int*
  // 4.1. Declare the instance
  Value<int*> pi1 = new int(50);
  pi1.Print("pi1");

  // 4.2. The test of copy constructor
  Value<int*> pi2 = pi1;
  pi2.Print("pi2");

  // 4.3. The test of copy operator
  pi2 = pi1;
  pi2.Print("pi2");

  // 4.4. Method Get()
  int* pi = pi2.Get();
  cout << "pi = " << *pi << endl;
}

 


Related topics