Pull to refresh

Трюки со специализацией шаблонов C++

C++ *
imageСпециализация шаблонов является одной из «сложных» фичей языка с++ и использутся в основном при создании библиотек. К сожалению, некоторые особенности специализации шаблонов не очень хорошо раскрыты в популярных книгах по этому языку. Более того, даже 53 страницы официального ISO стандарта языка, посвященные шаблонам, описывают интересные детали сумбурно, оставляя многое на «догадайтесь сами — это же очевидно». Под катом я постарался ясно изложить базовые принципы специализации шаблонов и показать как эти принципы можно использовать в построении магических заклинаний.


Hello World


Как мы привыкли использовать шаблоны? Используем ключевое слово template, затем в угловых скобках имена параметров шаблона, после чего тип и имя. Для параметров также указывают что это такое: тип (typename) или значение (например, int). Тип самого шаблона может быть класс (class), структура (struct — вообщем-то тоже класс) или функция (bool foo() и так далее). Например, простейший шаблонный класс 'A' можно задать вот так:
image
Через некоторое время мы захотим, чтобы наш класс для всех типов работал одинаково, а для какого-нибудь хитрого вроде int — по-другому. Фигня вопрос, пишем специализацию: выглядит так же как объявление но параметры шаблона в угловых скобках не указываем, вместо этого указываем конкретные аргументы шаблона после его имени:

template<> class A< int > {}; // здесь int - это аргумент шаблона

Готово, можно писать методы и поля специальной реализации для int. Такая специализация обычно называется полной (full specialization или explicit specialization). Для большинства практических задач большего не требуется. А если требуется, то…

Специализированный шаблон — это новый шаблон


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

Специализированный шаблон может иметь свои параметры шаблона


Дьявол, как известно, в деталях. То, что специализированный шаблонный класс это совсем-совсем новый и отдельный класс конечно интересно, но магии в этом мало. А магия есть в незначительном следствии — если это отдельный шаблонный класс, то он может иметь отдельные, никак не связанные с неспециализированным шаблонным классом параметры (параметры — это то, что после template в угловых скобках). Например, вот так:

template< typename S, typename U > class A< int > {};

Правда, именно такой код компилятор не скомпилирует — новые параметры шаблона S и U мы никак не используем, что для специализированного шаблонного класса запрещено (а то что это класс специализированный компилятор понимает потому, что у него такое же имя 'A' как у уже объявленного шаблонного класса). Компилятор даже специальную ошибку скажет: «explicit specialization is using partial specialization syntax, use template<> instead». Намекает, что если сказать нечего — то надо использовать template<> и не выпендриваться. Тогда для чего же в специализированном шаблонном классе можно использовать новые параметры? Ответ странный — для того, чтобы задать аргументы специализации (аргументы — это то, что после имени класса в угловых скобках). То есть специализируя шаблонный класс мы можем вместо простого и понятного int специализировать его через новые параметры:

template< typename S, typename U > class A< std::map< S, U > > {};

Такая странная запись скомпилируется. И при использовании получившегося шаблонного класса с std::map будет использована специализация, где тип ключа std::map будет доступен как параметр нового шаблона S, а тип значения std::map как U.

Такая специализация шаблона, при которой задается новый список параметров и через эти параметры задаются аргументы для специализации называется частичной специализацией (partial specialization). Почему «частичной»? Видимо потому, что изначально задумывалась как синтаксис для специализации шаблона не по всем аргументам. Пример, где шаблонный класс с двумя параметрами специализируется только по одному из них (специализация будет работать когда первый аргумент, T, будет указан как int. При этом второй аргумент может быть любым — для этого в частичной специализации введен новый параметр U и указан в списке аргументов для специализации):

template< typename T, typename S > class B {};
template< typename U > class B< int, U > {};

Магические последствия частичной специализации


Из двух вышеописанных свойств специализации шаблонов есть ряд интересных следствий. Например, при использовании частичной специализации можно, вводя новые параметры шаблона и описывая через них специализированные аргументы, разбивать составные типы на простейшие. В приведенном ниже примере специализированный шаблонный класс A будет использован, если аргументов шаблона является тип указателя на функцию. При этом через новые параметры шаблона S и U можно получить тип возвращаемого значения этой функции и тип ее аргумента:

template< typename S, typename U > class A< S(*)(U) > {};

А если в специализированном шаблоне объявить typedef или static const int (пользуясь тем, что это новый шаблон), то можно использовать его для извлечения нужной информации из типа. Например, мы используем шаблонный класс для хранения объектов и хотим получить размер переданного объекта или 0, если это указатель. В две строчки:

template< typename T > struct Get { const static int Size = sizeof( T ); };
template< typename S > struct Get< S* > { const static int Size = 0; };

Get< int >::Size  // например, 4
Get< int* >::Size // 0 - нашли указатель :)

Магия этого типа используется в основном в библиотеках: stl, boost, loki и так далее. Конечно, при высокоуровневом программировании использовать такие фокусы череповато — думаю, все помнят конструкцию для получения размера массива :). Но в библиотеках частичная специализация позволяет относительно просто реализовывать делегаты, события, сложные контейнеры и прочие иногда очень нужные и полезные вещи.

Коллеги, если найдете ошибку (а я, к сожалению, не гуру — могу ошибаться) или у Вас есть критика, вопросы али дополнения к вышеизложенному — буду рад комментариям.

Update: Обещанное продолжение тут.
Tags:
Hubs:
Total votes 47: ↑45 and ↓2 +43
Views 100K
Comments Comments 61