Шаблоны-члены
Шаблон функции или класса может быть членом обычного класса или шаблона класса. Определение шаблона-члена похоже на определение шаблона: ему предшествует ключевое слово template, за которым идет список параметров:
template <class T> class Queue { private: // шаблон класса-члена template <class Type> class CL { Type member; T mem; }; // ... public: // шаблон функции-члена template <class Iter> void assign( Iter first, Iter last ) { while ( ! is_empty() ) remove(); // вызывается Queue<T>::remove() for ( ; first != last; ++first ) add( *first ); // вызывается Queue<T>::add( const T & ) } |
}
(Отметим, что шаблоны-члены не поддерживаются компиляторами, написанными до принятия стандарта C++. Эта возможность была добавлена в язык для поддержки реализации абстрактных контейнерных типов, представленных в главе 6.)
Объявление шаблона-члена имеет собственные параметры. Например, у шаблона класса CL
есть параметр Type, а у шаблона функции assign() – параметр Iter. Помимо этого, в определении шаблона-члена могут использоваться параметры объемлющего шаблона класса. Например, у шаблона CL
есть член типа T, представляющего параметр включающего шаблона Queue.
Объявление шаблона-члена в шаблоне класса Queue означает, что конкретизация Queue
потенциально может содержать бесконечное число различных вложенных классов CL
функций-членов assign(). Так, конкретизированный экземпляр Queue<int>
включает вложенные типы:
Queue<int>::CL<char> |
Queue<int>::CL<string>
и вложенные функции:
void Queue<int>::assign( int *, int * ) void Queue<int>::assign( vector<int>::iterator, |
vector<int>::iterator )
Для шаблона-члена действуют те же правила доступа, что и для других членов класса. Так как шаблон CL
является закрытым членом шаблона Queue, то лишь функции-члены и друзья Queue
могут ссылаться на его конкретизации. С другой стороны, шаблон функции assign()
объявлен открытым членом и, значит, доступен во всей программе.
Шаблон-член конкретизируется при его использовании в программе. Например, assign()
конкретизируется в момент обращения к ней из main():
int main() { // конкретизация Queue<int> Queue<int> qi; // конкретизация Queue<int>::assign( int *, int * ) int ai[4] = { 0, 3, 6, 9 }; qi.assign( ai, ai + 4 ); // конкретизация Queue<int>::assign( vector<int>::iterator, // vector<int>::iterator ) vector<int> vi( ai, ai + 4 ); qi.assign( vi.begin(), vi.end() ); |
Шаблон функции assign(), являющийся членом шаблона класса Queue, иллюстрирует необходимость применения шаблонов-членов для поддержки контейнерных типов. Предположим, имеется очередь типа Queue<int>, в которую нужно поместить содержимое любого другого контейнера (списка, вектора или обычного массива), причем его элементы имеют либо тип int
(т.е. тот же, что у элементов очереди), либо приводимый к типу int. Шаблон-член assign()позволяет это сделать. Поскольку может быть использован любой контейнерный тип, то интерфейс assign()
программируется в расчете на употребление итераторов; в результате реализация оказывается не зависящей от фактического типа, на который итераторы указывают.
В функции main()
шаблон-член assign()
сначала конкретизируется типом int*, что позволяет поместить в qi
содержимое массива элементов типа int. Затем шаблон-член конкретизируется типом vector<int>::iterator – это дает возможность поместить в очередь qi содержимое вектора элементов типа int. Контейнер, содержимое которого помещается в очередь, не обязательно должен состоять из элементов типа int. Разрешен любой тип, который приводится к int. Чтобы понять, почему это так, еще раз посмотрим на определение assign():
template <class Iter> void assign( Iter first, Iter last ) { // удалить все элементы из очереди for ( ; first != last; ++first ) add( *first ); |
}
Вызываемая из assign()
функция add() – это функция-член Queue<Type>::add(). Если Queue
конкретизируется типом int, то у add()
будет следующий прототип:
void Queue<int>::add( const int &val );
Аргумент *first
должен иметь тип int
либо тип, которым можно инициализировать параметр-ссылку на const int. Преобразования типов допустимы. Например, если воспользоваться классом SmallInt из раздела 15.9, то содержимое контейнера, в котором хранятся элементы типа SmallInt, с помощью шаблона-члена assign()
помещается в очередь типа Queue<int>. Это возможно потому, что в классе SmallInt имеется конвертер для приведения SmallInt к int:
class SmallInt { public: SmallInt( int ival = 0 ) : value( ival ) { } // конвертер: SmallInt ==> int operator int() { return value; } // ... private: int value; }; int main() { // конкретизация Queue<int> Queue<int> qi; vector<SmallInt> vsi; // заполнить вектор // конкретизация // Queue<int>::assign( vector<SmallInt>::iterator, // vector<SmallInt>::iterator ) qi.assign( vsi.begin(), vsi.end() ); list<int*> lpi; // заполнить список // ошибка при конкретизации шаблона-члена assign(): // нет преобразования из int* в int qi.assign( lpi.begin(), lpi.end() ); |
Первая конкретизация assign()
правильна, так как существует неявное преобразование из типа SmallInt в тип int и, следовательно, обращение к add() корректно. Вторая же конкретизация ошибочна: объект типа int* не может инициализировать ссылку на тип const int, поэтому вызвать функцию add() невозможно.
Для контейнерных типов из стандартной библиотеки C++ имеется функция assign(), которая ведет себя так же, как функция-шаблон assign() для нашего класса Queue.
Любую функцию-член можно задать в виде шаблона. Это относится, в частности, к конструктору. Например, для шаблона класса Queue его можно определить следующим образом:
template <class T> class Queue { // ... public: // шаблон-член конструктора template <class Iter> Queue( Iter first, Iter last ) : front( 0 ), back( 0 ) { for ( ; first != last; ++first ) add( * first ); } |
Такой конструктор позволяет инициализировать очередь содержимым другого контейнера. У контейнерных типов из стандартной библиотеки C++ также есть предназначенные для этой цели конструкторы в виде шаблонов-членов. Кстати, в первом (в данном разделе) определении функции main() использовался конструктор-шаблон для вектора:
vector<int> vi( ai, ai + 4 );
Это определение конкретизирует шаблон конструктора для контейнера vector<int>
типом int*, что позволяет инициализировать вектор содержимым массива элементов типа int.
Шаблон-член, как и обычные члены, может быть определен вне определения объемлющего класса или шаблона класса. Так, являющиеся членами шаблон класса CL или шаблон функции assign()
могут быть следующим образом определены вне шаблона Queue:
template <class T> class Queue { private: template <class Type> class CL; // ... public: template <class Iter> void assign( Iter first, Iter last ); // ... }; template <class T> template <class Type> class Queue<T>::CL<Type> { Type member; T mem; }; template <class T> template <class Iter> void Queue<T>::assign( Iter first, Iter last ) { while ( ! is_empty() ) remove(); for ( ; first != last; ++first ) add( *first ); |
Определению шаблона-члена, которое находится вне определения объемлющего шаблона класса, предшествует список параметров объемлющего шаблона класса, а за ним должен следовать собственный такой список. Вот почему определение шаблона функции assign()
(члена шаблона класса Queue) начинается с
template <class T> template <class Iter>
Первый список параметров шаблона template <class T> относится к шаблону класса Queue. Второй – к самому шаблону-члену assign(). Имена параметров не обязаны совпадать с теми, которые указаны внутри определения объемлющего шаблона класса. Приведенная инструкция по-прежнему определяет шаблон-член assign():
template <class TT> template <class IterType>
void Queue<TT>::assign( IterType first, IterType last ) |