Аргументы шаблона для параметров-констант
Параметр шаблона класса может и не быть типом. На аргументы, подставляемые вместо таких параметров, накладываются некоторые ограничения. В следующем примере мы изменяем определение класса Screen (см. главу 13) на шаблон, параметризованный высотой и шириной:
template <int hi, int wid>
class Screen {
public:
Screen() : _height( hi ), _width( wid ), _cursor ( 0 ),
_screen( hi * wid, '#' )
{ }
// ...
private:
string _screen;
string::size_type _cursor;
short _height;
short _width;
};
typedef Screen<24,80> termScreen;
termScreen hp2621;
| Screen<8,24> ancientScreen;
Выражение, с которым связан параметр, не являющийся типом, должно быть константным, т.е. вычисляемым во время компиляции. В примере выше typedef termScreen ссылается на экземпляр шаблона Screen<24,80>, где аргумент шаблона для hi равен 24, а для wid – 80. В обоих случаях аргумент – это константное выражение.
Однако для шаблона BufPtr конкретизация приводит к ошибке, так как значение указателя, получающееся при вызове оператора new(), становится известно только во время выполнения:
template <int *ptr> class BufPtr { ... };
// ошибка: аргумент шаблона нельзя вычислить во время компиляции
| BufPtr< new int[24] > bp;
Не является константным выражением и значение неконстантного объекта. Его нельзя использовать в качестве аргумента для параметра-константы шаблона. Однако адрес любого объекта в области видимости пространства имен, в отличие от адреса локального объекта, является константным выражением (даже если спецификатор const отсутствует), поэтому его можно применять в качестве аргумента для параметра-константы. Константным выражением будет и значение оператора sizeof:
template <int size> Buf { ... };
template <int *ptr> class BufPtr { ... };
int size_val = 1024;
const int c_size_val = 1024;
Buf< 1024 > buf0; // правильно
Buf< c_size_val > buf1; // правильно
Buf< sizeof(size_val) > buf2; // правильно: sizeof(int)
BufPtr< &size_val > bp0; // правильно
// ошибка: нельзя вычислить во время компиляции
| Buf< size_val > buf3;
Вот еще один пример, иллюстрирующий использование параметра-константы для представления константного значения в определении шаблона, а также применение его аргумента для задания значения этого параметра:
template < class Type, int size >
class FixedArray {
public:
FixedArray( Type *ar ) : count( size )
{
for ( int ix = 0; ix < size; ++ix )
array[ ix ] = ar[ ix ];
}
private:
Type array[ size ];
int count;
};
int ia[4] = { 0, 1, 2, 3 };
| FixedArray< int, sizeof( is ) / sizeof( int ) > iA{ ia );
Выражения с одинаковыми значениями считаются эквивалентными аргументами для параметров-констант шаблона. Так, все три экземпляра Screen ссылаются на один и тот же конкретизированный из шаблона класс Screen<24,80>:
const int width = 24;
const int height = 80;
// все это Screen< 24, 80 >
Screen< 2*12, 40*2 > scr0;
Screen< 6+6+6+6, 20*2 + 40 > scr1;
| Screen< width, height > scr2;
Между типом аргумента шаблона и типом параметра-константы допустимы некоторые преобразования. Их множество является подмножеством преобразований, допустимых для аргументов функции:
· трансформации l-значений, включающие преобразование l-значения в r-значение, массива в указатель и функции в указатель:
template <int *ptr> class BufPtr { ... };
int array[10];
| BufPtr< array > bpObj; // преобразование массива в указатель
· преобразования квалификаторов:
template <const int *ptr> class Ptr { ... };
int iObj;
| Ptr< &iObj > pObj; // преобразование из int* в const int*
· расширения типов:
template <int hi, int wid> class Screen { ... };
const short shi = 40;
const short swi = 132;
| Screen< shi, swi > bpObj2; // расширения типа short до int
· преобразования целых типов:
template <unsigned int size> Buf{ ... };
| Buf< 1024 > bpObj; // преобразование из int в unsigned int
(Более подробно они описаны в разделе 9.3.)
Рассмотрим следующие объявления:
extern void foo( char * );
extern void bar( void * );
typedef void (*PFV)( void * );
const unsigned int x = 1024;
template <class Type,
unsigned int size,
PFV handler> class Array { ... };
Array<int, 1024U, bar> a0; // правильно: преобразование не нужно
Array<int, 1024U, foo> a1; // ошибка: foo != PFV
Array<int, 1024, bar> a2; // правильно: 1024 преобразуется в unsigned int
Array<int, 1024, bar> a3; // ошибка: foo != PFV
Array<int, x, bar> a4; // правильно: преобразование не нужно
| Array<int, x, foo> a5; // ошибка: foo != PFV
Объекты a0 и a4 класса Array определены правильно, так как аргументы шаблона точно соответствуют типам параметров. Объект a2 также определен правильно, потому что аргумент 1024 типа int приводится к типу unsigned int параметра-константы size с помощью преобразования целых типов. Объявления a1, a3 и a5 ошибочны, так как не существует преобразования между любыми двумя типами функций.
Приведение значения 0 целого типа к типу указателя недопустимо:
template <int *ptr>
class BufPtr { ... };
// ошибка: 0 имеет тип int
// неявное преобразование в нулевой указатель не применяется
| BufPtr< 0 > nil;
Упражнение 16.3
Укажите, какие из данных конкретизированных шаблонов действительно приводят к конкретизации:
template < class Type >
class Stack { };
void f1( Stack< char > ); // (a)
class Exercise {
// ...
Stack< double > &rsd; // (b)
Stack< int > si; // (c)
};
int main() {
Stack< char > *sc; // (d)
f1( *sc ); // (e)
int iObj = sizeof( Stack< string > ); // (f)
| }
Упражнение 16.4
Какие из следующих конкретизаций шаблонов корректны? Почему?
template < int *ptr > class Ptr ( ... };
template < class Type, int size > class Fixed_Array { ... };
| template < int hi, int wid > class Screen { ... };
(a) const int size = 1024;
| Ptr< &size > bp1;
(b) int arr[10];
Ptr< arr > bp2;
| (c) Ptr < 0 > bp3;
(d) const int hi = 40;
const int wi = 80;
| Screen< hi, wi+32 > sObj;
(e) const int size_val = 1024;
| Fixed_Array< string, size_val > fa1;
(f) unsigned int fasize = 255;
| Fixed_Array< int, fasize > fa2;
(g) const double db = 3.1415;
| Fixed_Array< double, db > fa3;
Функции-члены шаблонов классов
Как и для обычных классов, функция-член шаблона класса может быть определена либо внутри определения шаблона (и тогда называется встроенной), либо вне его. Мы уже встречались со встроенными функциями-членами при рассмотрении шаблона Queue. Например, конструктор Queue является встроенным, так как определен внутри определения шаблона класса:
template <class Type>
class Queue {
// ...
public:
// встроенный конструктор
Queue() : front( 0 ), back( 0 ) { }
// ...
| };
При определении функции-члена шаблона вне определения самого шаблона следует применять специальный синтаксис для обозначения того, членом какого именно шаблона является функция. Определению функции-члена должно предшествовать ключевое слово template, за которым следуют параметры шаблона. Так, конструктор Queue можно определить следующим образом:
template <class Type>
class Queue {
public:
Queue();
private:
// ...
};
template <class Type>
inline Queue<Type>::
| Queue( ) { front = back = 0; }
За первым вхождением Queue (перед оператором ::) следует список параметров, показывающий, какому шаблону принадлежит данная функция-член. Второе вхождение Queue в определение конструктора (после оператора ::) содержит имя функции-члена, за которым может следовать список параметров шаблона, хотя это и необязательно. После имени функции идет ее определение;. в нем могут быть ссылки на параметр шаблона Type всюду, где в определении обычной функции использовалось бы имя типа.
Функция-член шаблона класса сама является шаблоном. Стандарт C++ требует, чтобы она конкретизировалась только при вызове либо при взятии ее адреса. (Некоторые более старые компиляторы конкретизируют такие функции одновременно с конкретизацией самого шаблона класса.) При конкретизации функции-члена используется тип того объекта, для которого функция вызвана:
Queue<string> qs;
Объект qs имеет тип Queue<string>. При инициализации объекта этого класса вызывается конструктор Queue<string>. В данном случае аргументом, которым конкретизируется функция-член (конструктор), будет string.
Функция-член шаблона конкретизируется только при реальном использовании в программе (т.е. при вызове или взятии ее адреса). От того, в какой именно момент конкретизируется функция-член, зависит разрешение имен в ее определении (см. раздел 16.11) и объявление ее специализации (см. раздел 16.9).
Не нашли, что искали? Воспользуйтесь поиском по сайту:
©2015 - 2024 stydopedia.ru Все материалы защищены законодательством РФ.
|