int ai[4] = { 0, 3, 6, 9 };
qi.assign( ai, ai + 4 );
// конкретизация Queueint ::assign( vectorint ::iterator,
// vectorint ::iterator )
vectorint vi( ai, ai + 4 );
qi.assign( vi.begin(), vi.end() );
}
Шаблон функции assign(), являющийся членом шаблона класса Queue, иллюстрирует необходимость применения шаблонов-членов для поддержки контейнерных типов. Предположим, имеется очередь типа Queueint, в которую нужно поместить содержимое любого другого контейнера (списка, вектора или обычного массива), причем его элементы имеют либо тип int (т.е. тот же, что у элементов очереди), либо приводимый к типу int. Шаблон-член assign()позволяет это сделать. Поскольку может быть использован любой контейнерный тип, то интерфейс assign() программируется в расчете на употребление итераторов; в результате реализация оказывается не зависящей от фактического типа, на который итераторы указывают.
В функции main() шаблон-член assign() сначала конкретизируется типом int*, что позволяет поместить в qi содержимое массива элементов типа int. Затем шаблон-член конкретизируется типом vectorint::iterator - это дает возможность поместить в очередь qi содержимое вектора элементов типа int. Контейнер, содержимое которого помещается в очередь, не обязательно должен состоять из элементов типа int. Разрешен любой тип, который приводится к int. Чтобы понять, почему это так, еще раз посмотрим на определение assign():
template class Iter
void assign( Iter first, Iter last )
{
// удалить все элементы из очереди
for ( ; first != last; ++first )
add( *first );
}
Вызываемая из assign() функция add() - это функция-член QueueType::add(). Если Queue конкретизируется типом int, то у add() будет следующий прототип:
void Queueint::add( const int &val );
Аргумент *first должен иметь тип int либо тип, которым можно инициализировать параметр-ссылку на const int. Преобразования типов допустимы. Например, если воспользоваться классом SmallInt из раздела 15.9, то содержимое контейнера, в котором хранятся элементы типа SmallInt, с помощью шаблона-члена assign() помещается в очередь типа Queueint. Это возможно потому, что в классе SmallInt имеется конвертер для приведения SmallInt к int:
class SmallInt {
public:
SmallInt( int ival = 0 ) : value( ival ) { }
// конвертер: SmallInt == int
operator int() { return value; }
// ...
private:
int value;
};
int main()
{
// конкретизация Queueint
Queueint qi;
vectorSmallInt vsi;
// заполнить вектор
// конкретизация
// Queueint ::assign( vectorSmallInt ::iterator,
// vectorSmallInt ::iterator )
qi.assign( vsi.begin(), vsi.end() );
listint* 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.
Шаблон-член, как и обычные члены, может быть определен вне определения объемлющего класса или шаблона класса. Так, являющиеся членами шаблон класса CL или шаблон функции assign() могут быть следующим образом определены вне шаблона Queue: