Специализация шаблонов, которой нет

В некоторых случаях бывает необходимо предоставить реализацию шаблона для конкретного типа (или набора типов), так чтобы она отличалась от общего варианта. Например, обычно имеет смысл подготовить особую версию функции swap (обмена значениями) для указателей или массивов. В подобных случаях С++ позволяет сделать специализацию шаблона, то есть определить его версию, в которой обобщенный параметр типа T заменен на требуемый конкретный тип.

При специализации шаблонов функций и методов должны быть заданы конкретные типы для всех параметров. Это называется полной специализацией.

В случае шаблонов объектных типов C++ специализация может быть не только полной, но и частичной: при ней уточняется тип лишь некоторых из параметров (а остальные будут выводиться или указываться при создании экземпляра шаблона). Частичных специализаций может быть несколько: единственное условие для этого — каждая специализация должна описывать уникальное сочетание типов.

К сожалению, в MQL5 нет специализации в полном смысле этого слова.

Специализация шаблонной функции ничем не отличается от перегрузки. Например, при наличии следующего шаблона func:

template<typename T>
void func(T t) { ... }

допускается предоставить его особую реализацию для заданного типа (такого как string) в одной из форм:

// явная специализация
template<>
void func(string t) { ... }

или:

// обычная перегрузка
void func(string t) { ... }

Должна быть выбрана только одна из форм. Иначе получим ошибку компиляции "'func' - функция уже определена и имеет тело" ("'func' - function already defined and has body").

Что касается специализации классов, то неким эквивалентом их частичной специализации можно считать наследование от шаблонов с указанием конкретных типов для части шаблонных параметров. В производном классе методы шаблона можно переопределить.

В следующем примере (TemplatesExtended.mq5) показано несколько вариантов использования параметров шаблонов в качестве родительских типов, в том числе и случаи, когда один из них прописан конкретным.

#define RTTI Print(typename(this))
   
class Base
{
public:
   Base() { RTTI; }
};
   
template<typename T
class Derived : public T
{
public:
   Derived() { RTTI; }
}; 
   
template<typename T
class Base1
{
   Derived<Tobject;
public:
   Base1() { RTTI; }
}; 
   
template<typename T>                // полная "специализация"
class Derived1 : public Base1<Base// задан 1 из 1 параметра 
{
public:
   Derived1() { RTTI; }
}; 
   
template<typename T,typename E
class Base2 : public T
{
public:
   Base2() { RTTI; }
}; 
   
template<typename T>                    // частичная "специализация"
class Derived2 : public Base2<T,string// задан 1 из 2 параметров 
{
public:
   Derived2() { RTTI; }
};

Инстанцирование объекта по шаблону обеспечим с помощью переменной:

   Derived2<Derived1<Base>> derived2;

Отладочный вывод типов в журнал с помощью макроса RTTI дает следующий результат:

Base
Derived<Base>
Base1<Base>
Derived1<Base>
Base2<Derived1<Base>,string>
Derived2<Derived1<Base>>

При разработке библиотек, которые поставляются в виде закрытого двоичного кода, необходимо обеспечить явное создание экземпляров шаблонов для всех типов, с которыми предполагается работа будущими пользователями библиотеки. Сделать это можно, вызвав явным образом шаблоны функций и создав объекты с указанием параметров-типов в какой-либо вспомогательной функции, например, привязанной к инициализации глобальной переменной.