Явная (полная) специализация шаблона
Позволяет настроить код шаблона для заданного набора аргументов шаблона.
Синтаксис
template <> объявление
|
|||||||||
Любое из следующего может быть полностью специализировано:
- шаблон функции
- шаблон класса
- (начиная с C++14) шаблон переменной
- функция-элемент шаблона класса
- статический элемент-данных шаблона класса
- класс-элемент шаблона класса
- элемент перечисление шаблона класса
- шаблон класса-элемента класса или шаблона класса
- шаблон функции-элемента класса или шаблона класса
- шаблонная переменная элемент класса или шаблона класса (начиная с C++14)
Например,
#include <type_traits>
template<typename T> // первичный шаблон
struct is_void : std::false_type
{
};
template<> // явная специализация для T = void
struct is_void<void> : std::true_type
{
};
int main()
{
static_assert(is_void<char>::value == false,
"для любого типа T, кроме void, класс является производным от false_type");
static_assert(is_void<void>::value == true,
"но когда T имеет тип void, класс является производным от true_type");
}
В деталях
Явная специализация может быть объявлена в любой области видимости, где может быть определён её первичный шаблон. (которая может отличаться от области видимости, в которой определён первичный шаблон; например, с внеклассовой специализацией шаблона элемента). Явная специализация должна быть указана после объявления неспециализированного шаблона.
namespace N {
template<class T> class X { /*...*/ }; // первичный шаблон
template<> class X<int> { /*...*/ }; // специализация в том же пространстве имён
template<class T> class Y { /*...*/ }; // первичный шаблон
template<> class Y<double>; // предварительно объявленная специализация для double
}
template<>
class N::Y<double> { /*...*/ }; // OK: специализация в том же пространстве имён
Специализация должна быть объявлена перед первым использованием, которое вызовет неявное создание экземпляра, в каждой единице трансляции, где происходит такое использование:
class String {};
template<class T> class Array { /*...*/ };
template<class T> void sort(Array<T>& v) { /*...*/ } // первичный шаблон
void f(Array<String>& v) {
sort(v); // неявно создаёт sort(Array<String>&),
} // используя первичный шаблон для sort()
template<> // ОШИБКА: явная специализация sort(Array<String>)
void sort<String>(Array<String>& v); // после неявного создания экземпляра
Специализация шаблона, которая была объявлена, но не определена, может использоваться так же, как любой другой неполный тип (например могут использоваться указатели и ссылки на него)
template<class T> class X; // первичный шаблон
template<> class X<int>; // специализация (объявлена, не определена)
X<int>* p; // OK: указатель на неполный тип
X<int> x; // ошибка: объект неполного типа
Является ли явная специализация шаблона функции или переменной (начиная с C++14) inline/constexpr (начиная с C++11)/constinit/consteval (начиная с C++20) определяется самой явной специализацией, независимо от того, объявлен ли первичный шаблон с этим спецификатором. Точно так же атрибуты, появляющиеся в объявлении шаблона, не влияют на явную специализацию этого шаблона: (начиная с C++11)
template<class T>
void f(T) { /* ... */ }
template<>
inline void f<>(int) { /* ... */ } // OK, inline
template<class T>
inline T g(T) { /* ... */ }
template<>
int g<>(int) { /* ... */ } // OK, не inline
template<typename>
[[noreturn]] void h([[maybe_unused]] int i);
template<> void h<int>(int i)
{
// [[noreturn]] не имеет эффекта, но [[maybe_unused]] имеет
}
Явные специализации шаблонов функций
При специализации шаблона функции аргументы этого шаблона могут быть опущены, если вывод аргументов шаблона может предоставить их из аргументов функции:
template<class T> class Array { /*...*/ };
template<class T> void sort(Array<T>& v); // первичный шаблон
template<> void sort(Array<int>&); // специализация для T = int
// не нужно писать
// template<> void sort<int>(Array<int>&);
Функция с тем же именем и тем же списком аргументов, что и специализация, не является специализацией. (смотрите перегрузку шаблона в шаблонах функций)
Явная специализация шаблона функции является встроенной, только если она объявлена со спецификатором inline (или определена как удалённая), и не имеет значения, является ли первичный шаблон встроенным.
Аргументы функции по умолчанию не могут быть указаны в явных специализациях шаблонов функций, шаблонов функций-элементов и функций-элементов шаблонных классов, когда класс создаётся неявно.
Явная специализация не может быть friend объявлением.
| Этот раздел не завершён Причина: просмотреть требования к спецификации исключений в разных версиях C++ |
Элементы специализаций
При определении элемента явно специализированного шаблона класса вне тела класса синтаксис template <> не используется, за исключением случаев, когда он является элементом явно специализированного шаблона класса-элемента, который специализирован как шаблон класса, потому что в противном случае синтаксис потребовал бы, чтобы такое определение начиналось с template<параметры>, требуемого вложенным шаблоном
template< typename T>
struct A {
struct B {}; // класс-элемент
template<class U>
struct C { }; // шаблон класса-элемента
};
template<> // специализация
struct A<int> {
void f(int); // функция-элемент специализации
};
// template<> не используется для элемента специализации
void A<int>::f(int) { /* ... */ }
template<> // специализация класса-элемента
struct A<char>::B {
void f();
};
// template<> не используется для элемента специализированного класса-элемента
void A<char>::B::f() { /* ... */ }
template<> // специализация шаблона класса-элемента
template<class U>
struct A<char>::C {
void f();
};
// template<> используется при определении элемента явно специализированного
// шаблона класса-элемента, специализированного как шаблон класса
template<>
template<class U> void A<char>::C<U>::f() { /* ... */ }
Явная специализация статического элемента данных шаблона, это определение, если объявление включает инициализатор; в противном случае это объявление. Эти определения должны использовать фигурные скобки для инициализации по умолчанию:
template<> X Q<int>::x; // объявление статического элемента
template<> X Q<int>::x (); // ошибка: объявление функции
template<> X Q<int>::x {}; // определение статического элемента, инициализированного
// по умолчанию
Элемент или шаблон-элемент шаблона класса может быть явно специализирован для данного неявного создания экземпляра шаблона класса, даже если элемент или шаблон-элемент определён в определении шаблона класса.
template<typename T>
struct A {
void f(T); // элемент, объявленный в первичном шаблоне
void h(T) {} // элемент, определённый в первичном шаблоне
template<class X1> void g1(T, X1); // шаблон-элемент
template<class X2> void g2(T, X2); // шаблон-элемент
};
// специализация элемента
template<> void A<int>::f(int);
// специализация элемента ОК, даже если она определена в классе
template<> void A<int>::h(int) {}
// определение шаблона-элемента вне класса
template<class T>
template<class X1> void A<T>::g1(T, X1) { }
// специализация шаблона-элемента
template<>
template<class X1> void A<int>::g1(int, X1);
// специализация шаблона-элемента
template<>
template<> void A<int>::g2<char>(int, char); // для X2 = char
// то же самое, используя вывод аргументов шаблона (X1 = char)
template<>
template<> void A<int>::g1(int, char);
Элемент или шаблон элемента могут быть вложены в несколько шаблонов включающих классов. В явной специализации для такого элемента существует template<> для каждого
включающего явно специализированного шаблона класса.
template<class T1> struct A {
template<class T2> struct B {
template<class T3>
void mf();
};
};
template<> struct A<int>;
template<> template<> struct A<char>::B<double>;
template<> template<> template<> void A<char>::B<char>::mf<double>();
В таком вложенном объявлении некоторые уровни могут оставаться неспециализированными (за исключением того, что они не могут специализировать шаблон-элемент класса, если его включающий класс неспециализирован). Для каждого из этих уровней объявление требует template<аргументы>, потому что такие специализации сами являются шаблонами:
template <class T1> class A {
template<class T2> class B {
template<class T3> void mf1(T3); // шаблон-элемент
void mf2(); // нешаблонный элемент
};
};
// специализация
template<> // для специализированного A
template<class X> // для неспециализированного B
class A<int>::B {
template <class T> void mf1(T);
};
// специализация
template<> // для специализированного A
template<> // для специализированного B
template<class T> // для неспециализированной mf1
void A<int>::B<double>::mf1(T t) { }
// ОШИБКА: B<double> является специализированным и является шаблоном-элементом,
// поэтому его включающий A также должен быть специализированным
template<class Y>
template<> void A<Y>::B<double>::mf2() { }
Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
| Номер | Применён | Поведение в стандарте | Корректное поведение |
|---|---|---|---|
| CWG 531 | C++98 | синтаксис определения элементов явных специализаций в области пространства имён не был специфицирован |
специфицирован |
| CWG 727 | C++98 | полные специализации не разрешены в области видимости класса, даже если существуют частичные |
полная специализация разрешена в любой области видимости |
| CWG 730 | C++98 | шаблоны-элементы классов, не являющихся шаблонами, не могли быть полностью специализированными |
позволено |
| CWG 2478 | C++20 | было неясно, переносятся ли constinit и consteval основного шаблона в его явные специализации
|
не переносятся |
| CWG 2604 | C++11 | было неясно, переносятся ли атрибуты основного шаблона в его явные специализации |
не переносятся |