Явная (полная) специализация шаблона
Позволяет настроить код шаблона для заданного набора аргументов шаблона.
Содержание |
[править]Синтаксис
template <> объявление | |||||||||
Любое из следующего может быть полностью специализировано:
- шаблон функции
- шаблон класса
- (начиная с C++14)шаблон переменной
- функция-элемент шаблона класса
- статический элемент-данных шаблона класса
- класс-элемент шаблона класса
- элемент перечисление шаблона класса
- шаблон класса-элемента класса или шаблона класса
- шаблон функции-элемента класса или шаблона класса
- шаблонная переменная элемент класса или шаблона класса(начиная с C++14)
Например,
#include <type_traits> template<typename T>// первичный шаблонstruct is_void :std::false_type{};template<>// явная специализация для T = voidstruct 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<>inlinevoid 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<>// для специализированного Atemplate<class X>// для неспециализированного Bclass A<int>::B{template<class T>void mf1(T);}; // специализацияtemplate<>// для специализированного Atemplate<>// для специализированного Btemplate<class T>// для неспециализированной mf1void 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 | было неясно, переносятся ли атрибуты основного шаблона в его явные специализации | не переносятся |