<< Пред.           стр. 46 (из 121)           След. >>

Список литературы по разделу

 // ошибка: спецификатор inline не на месте
 inline
 template
  Type min( Array, int );
 Упражнение 10.1
 Определите, какие из данных определений шаблонов функций неправильны. Исправьте ошибки.
 (a) template
  void foo( T, U, V );
 
 (b) template
  T foo( int *T );
 
 (c) template
  T1 foo( T2, T3 );
 
 (d) inline template
  T foo( T, unsigned int* );
 
 (e) template
  void foo( myT, myT );
 
 (f) template
  foo( T, T );
 
 (g) typedef char Ctype;
  template
  Ctype foo( Ctype a, Ctype b );
 Упражнение 10.2
 Какие из повторных объявлений шаблонов ошибочны? Почему?
 (a) template
  Type bar( Type, Type );
 
  template
  Type bar( Type, Type );
 
 (b) template
  void bar( T1, T2 );
 
  template
  void bar( C1, C2 );
 Упражнение 10.3
 Перепишите функцию putValues() из раздела 7.3.3 в виде шаблона. Параметризуйте его так, чтобы было два параметра шаблона (для типа элементов массива и для размера массива) и один параметр функции, являющийся ссылкой на массив. Напишите определение шаблона функции.
 10.2. Конкретизация шаблона функции
 Шаблон функции описывает, как следует строить конкретные функции, если задано множество фактических типов или значений. Процесс конструирования называется конкретизацией шаблона. Выполняется он неявно, как побочный эффект вызова или взятия адреса шаблона функции. Например, в следующей программе min() конкретизируется дважды: один раз для массива из пяти элементов типа int, а другой – для массива из шести элементов типа double:
 // определение шаблона функции min()
 // с параметром-типом Type и параметром-константой size
 
 template
  Type min( Type (&r_array)[size] )
 {
 
  Type min_val = r_array[0];
  for ( int i = 1; i < size; ++i )
  if ( r_array[i] < min_val )
  min_val = r_array[i];
 
  return min_val;
 }
 
 // size не задан -- ok
 // size = число элементов в списке инициализации
 int ia[] = { 10, 7, 14, 3, 25 };
 
 double da[6] = { 10.2, 7.1, 14.5, 3.2, 25.0, 16.8 };
 
 #include
 int main()
 {
  // конкретизация min() для массива из 5 элементов типа int
  // подставляется Type => int, size => 5
  int i = min( ia );
  if ( i != 3 )
  cout << "??oops: integer min() failed\n";
  else cout << "!!ok: integer min() worked\n";
 
  // конкретизация min() для массива из 6 элементов типа double
  // подставляется Type => double, size => 6
  double d = min( da );
  if ( d != 3.2 )
  cout << "??oops: double min() failed\n";
  else cout << "!!ok: double min() worked\n";
  return 0;
 }
 Вызов
 int i = min( ia );
 приводит к конкретизации следующего экземпляра функции min(), в котором Type заменено на int, а size на 5:
 int min( int (&r_array)[5] )
 {
  int min_val = r_array[0];
  for ( int i = 1; i < 5; ++i )
  if ( r_array[i] < min_val )
  min_val = r_array[i];
 
  return min_val;
 }
 Аналогично вызов
 double d = min( da );
 конкретизирует экземпляр min(), в котором Type заменено на double, а size на 6:
 В качестве формальных параметров шаблона функции используются параметр-тип и параметр-константа. Для определения фактического типа и значения константы, которые надо подставить в шаблон, исследуются фактические аргументы, переданные при вызове функции. В нашем примере для идентификации аргументов шаблона при конкретизации используются тип ia (массив из пяти int) и da (массив из шести double). Процесс определения типов и значений аргументов шаблона по известным фактическим аргументам функции называется выведением (deduction) аргументов шаблона. (В следующем разделе мы расскажем об этом подробнее. А в разделе 10.4 речь пойдет о возможности явного задания аргументов.)
 Шаблон конкретизируется либо при вызове, либо при взятии адреса функции. В следующем примере указатель pf инициализируется адресом конкретизированного экземпляра шаблона. Его аргументы определяются путем исследования типа параметра функции, на которую указывает pf:
 template
  Type min( Type (&p_array)[size] ) { /* ... */ }
 
 // pf указывает на int min( int (&)[10] )
 int (*pf)(int (&)[10]) = &min;
 Тип pf – это указатель на функцию с параметром типа int(&)[10], который определяет тип аргумента шаблона Type и значение аргумента шаблона size при конкретизации min(). Аргумент шаблона Type будет иметь тип int, а значением аргумента шаблона size будет 10. Конкретизированная функция представляется как min(int(&)[10]), и указатель pf адресует именно ее.
 Когда берется адрес шаблона функции, контекст должен быть таким, чтобы можно было однозначно определить типы и значения аргументов шаблона. Если сделать это не удается, компилятор выдает сообщение об ошибке:
 template
  Type min( Type (&r_array)[size] ) { /* ... */ }
 
 typedef int (&rai)[10];
 typedef double (&rad)[20];
 
 void func( int (*)(rai) );
 void func( double (*)(rad) );
 
 int main() {
 
 // ошибка: как конкретизировать min()?
  func( &min );
 }
 Функция func() перегружена и тип ее параметра не позволяет однозначно определить ни аргумент шаблона Type, ни значение аргумента шаблона size. Результатом конкретизации вызова func() может быть любая из следующих функций:
 min( int (*)(int(&)[10]) )
 min( double (*)(double(&)[20]) )
 Поскольку однозначно определить аргументы функции func() нельзя, взятие адреса конкретизированного шаблона в таком контексте приводит к ошибке компиляции.
 Этого можно избежать, если использовать явное приведение типов для указания типа аргумента:
 int main() {
  // правильно: с помощью явного приведения указывается тип аргумента
  func( static_cast< double(*)(rad) >(&min) );
 }
 Лучше, однако, применять явное задание аргументов шаблона, как будет показано в разделе 10.4.
 10.3. Вывод аргументов шаблона А
 При вызове шаблона функции типы и значения его аргументов определяются путем исследования типов фактических аргументов функции. Этот процесс называется выводом аргументов шаблона.
 Параметром функции в шаблоне min() является ссылка на массив элементов типа Type:
 template
  Type min( Type (&r_array)[size] ) { /* ... */ }
 Для сопоставления с формальным параметром функции фактический аргумент также должен быть l-значением, представляющим тип массива. Следующий вызов ошибочен, так как pval имеет тип int*, а не является l-значением типа “массив int”.
 void f( int pval[9] ) {
  // ошибка: Type (&)[] != int*
  int jval = min( pval );
 }
 При выводе аргументов шаблона не принимается во внимание тип значения, возвращаемого конкретизированным шаблоном функции. Например, если вызов min() записан так:
 double da[8] = { 10.3, 7.2, 14.0, 3.8, 25.7, 6.4, 5.5, 16.8 };
 int i1 = min( da );
 то конкретизированный экземпляр min() имеет параметр типа “указатель на массив из восьми double” и возвращает значение типа double. Перед инициализацией i1 это значение приводится к типу int. Однако тот факт, что результат вызова min() используется для инициализации объекта типа int, не влияет на вывод аргументов шаблона.
 Чтобы процесс такого вывода завершился успешно, тип фактического аргумента функции не обязательно должен совпадать с типом соответствующего формального параметра. Допустимы три вида преобразований типа: трансформация l-значения, преобразование спецификаторов и приведение к базовому классу, конкретизированному из шаблона класса. Рассмотрим последовательно каждое из них.
 Напомним, что трансформация l-значения – это либо преобразование l-значения в r-значение, либо преобразование массива в указатель, либо преобразование функции в указатель (все они рассматривались в разделе 9.3). Для иллюстрации влияния такой трансформации на вывод аргументов шаблона рассмотрим функцию min2() c одним параметром шаблона Type и двумя параметрами функции. Первый параметр min2() – это указатель на тип Type*. size теперь не является параметром шаблона, как в определении min(), вместо этого он стал параметром функции, а его значение должно быть явно передано при вызове:
 template
  // первый параметр имеет тип Type*
  Type min2( Type* array, int size )
 {
  Type min_val = array[0];
  for ( int i = 1; i < size; ++i )
  if ( array[i] < min_val )
  min_val = array[i];
 
  return min_val;
 }
 min2() можно вызвать, передав в качестве первого аргумента массив из четырех int, как в следующем примере:
 int ai[4] = { 12, 8, 73, 45 };
 
 int main() {
  int size = sizeof (ai) / sizeof (ai[0]);
 
  // правильно: преобразование массива в указатель
  min2( ai, size );
 }
 Фактический аргумент функции ai имеет тип “массив из четырех int” и не совпадает с типом соответствующего формального параметра Type*. Однако, поскольку преобразование массива в указатель допустимо, то аргумент ai приводится к типу int* еще до вывода аргумента шаблона Type, для которого затем выводится тип int, и шаблон конкретизирует функцию min2(int*, int).
 Преобразование спецификаторов добавляет const или volatile к указателям (такие трансформации также рассматривались в разделе 9.3). Для иллюстрации влияния преобразования спецификаторов на вывод аргументов шаблона рассмотрим min3() с первым параметром функции типа const Type*:
 template
  // первый параметр имеет тип const Type*
  Type min3( const Type* array, int size ) {
  // ...
 }
 min3() можно вызвать, передав int* в качестве первого фактического аргумента, как в следующем примере:
 int *pi = &ai;
 // правильно: приведение спецификаторов к типу const int*
 int i = min3( pi, 4 );
 Фактический аргумент функции pi имеет тип “указатель на int” и не совпадает с типом формального параметра const Type*. Однако, поскольку преобразование спецификаторов допустимо, то он приводится к типу const int* еще до вывода аргумента шаблона Type, для которого затем выводится тип int, и шаблон конкретизирует функцию min3(const int*, int).
 Теперь обратимся к преобразованию в базовый класс, конкретизированный из шаблона класса. Вывод аргументов шаблона можно выполнить, если тип формального параметра функции является таким шаблоном, а фактический аргумент – базовый класс, конкретизированный из него. Чтобы проиллюстрировать такое преобразование, рассмотрим новый шаблон функции min4() с параметром типа Array&, где Array – это шаблон класса, определенный в разделе 2.5. (В главе 16 шаблоны классов обсуждаются во всех деталях.)
 template
  class Array { /* ... */ }
 
 template
  Type min4( Array& array )
 {
  Type min_val = array[0];
  for ( int i = 1; i < array.size(); ++i )
  if ( array[i] < min_val )
  min_val = array[i];
 
  return min_val;
 }
 min4() можно вызвать, передав в качестве первого аргумента ArrayRC, как показано в следующем примере. (ArrayRC – это шаблон класса, также определенный в главе 2; наследование классов подробно рассматривается в главах 17 и 18.)
 template
  class ArrayRC : public Array { /* ... */ };
 
 int main() {
  ArrayRC ia_rc(10);
  min4( ia_rc );
 }
 Фактический аргумент ia_rc имеет тип ArrayRC. Он не совпадает с типом формального параметра Array&. Но одним из базовых классов для ArrayRC является Array, так как он конкретизирован из шаблона класса, указанного в качестве формального параметра функции. Поскольку фактический аргумент является производным классом, то его можно использовать при выводе аргументов шаблона. Таким образом, перед выводом аргумент функции ArrayRC преобразуется в тип Array, после чего для аргумента шаблона Type выводится тип int и конкретизируется функция min4(Array&).
 В процессе вывода одного аргумента шаблона могут принимать участие несколько аргументов функции. Если параметр шаблона встречается в списке параметров функции более одного раза, то каждый выведенный тип должен точно соответствовать типу, выведенному для того же аргумента шаблона в первый раз:
 template T min5( T, T ) { /* ... */ }
 unsigned int ui;
 
 int main() {
  // ошибка: нельзя конкретизировать min5( unsigned int, int )
  // должно быть: min5( unsigned int, unsigned int ) или
  // min5( int, int )
  min5( ui, 1024 );
 }
 Оба фактических аргумента функции должны иметь один и тот же тип: либо int, либо unsigned int, поскольку в шаблоне они принадлежат к одному типу T. Аргумент шаблона T, выведенный из первого аргумента функции, – это int. Аргумент же шаблона T, выведенный из второго аргумента функции, – это unsigned int. Поскольку они оказались разными, процесс вывода завершается неудачей и при конкретизации шаблона выдается сообщение об ошибке. (Избежать ее можно, если явно задать аргументы шаблона при вызове функции min5(). В разделе 10.4 мы увидим, как это делается.)
 Ограничение на допустимые типы преобразований относится только к тем фактическим параметрам функции, которые принимают участие в выводе аргументов шаблона. К остальным аргументам могут применяться любые трансформации. В следующем шаблоне функции sum() есть два формальных параметра. Фактический аргумент op1 для первого параметра участвует в выводе аргумента Type шаблона, а второй фактический аргумент op2 – нет.
 template
  Type sum( Type op1, int op2 ) { /* ... */ }
 Поэтому при конкретизации шаблона функции sum() его можно подвергать любым трансформациям. (Преобразования типов, применимые к фактическим аргументам функции, описываются в разделе 9.3.) Например:
 int ai[] = { ... };
 double dd;
 int main() {
  // конкретизируется sum( int, int )
  sum( ai[0], dd );
 }
 Тип второго фактического аргумента функции dd не соответствует типу формального параметра int. Но это не мешает конкретизировать шаблон функции sum(), поскольку тип второго аргумента фиксирован и не зависит от параметров шаблона. Для этого вызова конкретизируется функция sum(int,int). Аргумент dd приводится к типу int с помощью преобразования целого типа в тип с плавающей точкой.
 Таким образом, общий алгоритм вывода аргументов шаблона можно сформулировать следующим образом:
 По очереди исследуется каждый фактический аргумент функции, чтобы выяснить, присутствует ли в соответствующем формальном параметре какой-нибудь параметр шаблона.
 Если параметр шаблона найден, то путем анализа типа фактического аргумента выводится соответствующий аргумент шаблона.
 Тип фактического аргумента функции не обязан точно соответствовать типу формального параметра. Для приведения типов могут быть применены следующие преобразования:
 трансформации l-значения
 преобразования спецификаторов
 приведение производного класса к базовому при условии, что формальный параметр функции имеет вид T& или T*, где список аргументов args содержит хотя бы один параметр шаблона.
 Если один и тот же параметр шаблона найден в нескольких формальных параметрах функций, то аргумент шаблона, выведенный по каждому из соответствующих фактических аргументов, должен быть одним и тем же.
 Упражнение 10.4
 Назовите два типа преобразований, которые можно применять к фактическим аргументам функций, участвующим в процессе вывода аргументов шаблона.
 Упражнение 10.5
 Пусть даны следующие определения шаблонов:
 template
  Type min3( const Type* array, int size ) { /* ... */ }
 template
  Type min5( Type p1, Type p2 ) { /* ... */ }
 Какие из приведенных ниже вызовов ошибочны? Почему?
 double dobj1, dobj2;
 float fobj1, fobj2;
 char cobj1, cobj2;
 int ai[5] = { 511, 16, 8, 63, 34 };
 
 (a) min5( cobj2, 'c' );
 (b) min5( dobj1, fobj1 );
 (c) min3( ai, cobj1 );
 10.4. Явное задание аргументов шаблона A
 В некоторых ситуациях автоматически вывести типы аргументов шаблона невозможно. Как мы видели на примере шаблона функции min5(), если процесс вывода дает два различных типа для одного и того же параметра шаблона, то компилятор сообщает об ошибке – неудачном выводе аргументов.
 В таких ситуациях приходится подавлять механизм вывода и задавать аргументы явно, указывая их с помощью заключенного в угловые скобки списка разделенных запятыми значений, который следует после имени конкретизируемого шаблона функции. Например, если мы хотим задать тип unsigned int в качестве значения аргумента шаблона T в рассмотренном выше примере использования min5(), то нужно записать вызов конкретизируемого шаблона так:
 // конкретизируется min5( unsigned int, unsigned int )
 min5< unsigned int >( ui, 1024 );
 В этом случае список аргументов шаблона явно задает их типы. Поскольку аргумент шаблона теперь известен, вызов функции больше не приводит к ошибке.
 Обратите внимание, что при вызове функции min5() второй аргумент равен 1024, т.е. имеет тип int. Так как тип второго формального параметра функции при явном задании аргумента шаблона установлен в unsigned int, то второй фактический параметр функции приводится к типу unsigned int с помощью стандартного преобразования целых типов.
 В предыдущем разделе мы говорили, что в процессе вывода аргументов шаблона к фактическим аргументам функции разрешается применять только ограниченное множество преобразований типов. Трансформация int в unsigned int в это множество не входит. Но если аргументы шаблона задаются явно, выполнять вывод типов не нужно, поскольку они уже зафиксированы. Следовательно, при явном задании аргументов шаблона для приведения типов фактических аргументов функции к типам формальных параметров можно применять любые стандартные преобразования.
 Помимо разрешения любых преобразований фактических аргументов функции, явное задание аргументов шаблона помогает избежать и других проблем, встающих перед программистом. Рассмотрим следующую задачу. Мы хотим определить шаблон функции с именем sum() так, чтобы его конкретизация возвращала значения типа, достаточно большого для представления суммы двух значений любых двух типов, переданных в любом порядке. Как это сделать? Какой тип возвращаемого значения следует задать?
 // каким должен быть тип возвращаемого значения: T или U
 template
  ??? sum( T, U );
 В нашем случае нельзя использовать ни тот, ни другой параметрический тип, иначе мы неизбежно допустим ошибку:
 char ch; unsigned int ui;
 
 // ни T, ни U нельзя использовать в качестве типа возвращаемого значения
 sum( ch, ui ); // правильно: U sum( T, U );
 sum( ui, ch ); // правильно: T sum( T, U );
 Решение заключается в том, чтобы ввести в шаблон третий параметр для обозначения типа возвращаемого значения:
 // T1 не появляется в списке параметров шаблона функции
 template
  T1 sum( T2, T3 );
 Поскольку тип возвращаемого значения может отличаться от типов аргументов функции, T1 не упоминается в списке формальных параметров. Это потенциальная проблема, так как тип T1 не может быть выведен из фактических аргументов функции. Однако, если при конкретизации sum() мы зададим аргументы шаблона явно, то избегнем сообщения компилятора о невозможности вывести T1. Например:
 typedef unsigned int ui_type;
 ui_type calc( char ch, ui_type ui ) {
 
  // ...
  // ошибка: невозможно вывести T1
  ui_type loc1 = sum( ch, ui );
 
  // правильно: аргументы шаблона заданы явно
  // T1 и T3 - это unsigned int, T2 - это char
  ui_type loc2 = sum< ui_type, ui_type >( ch, ui );
 }
 Не хватает возможности явно задать T1, но не T2 и T3, поскольку их можно вывести из аргументов функции при вызове.
 При явном задании аргументов шаблона необходимо перечислять только те, которые не могут быть выведены автоматически. Но, как и в случае аргументов функции со значениями по умолчанию, опускать можно исключительно “хвостовые”:
 // правильно: T3 - это unsigned int
 // T3 выведен из типа ui
 ui_type loc3 = sum< ui_type, char >( ch, ui );
 
 // правильно: T2 - это char, T3 - unsigned int
 // T2 и T3 выведены из типа pf
 ui_type (*pf)( char, ui_type ) = &sum< ui_type >;
 
 // ошибка: опускать можно только “хвостовые” аргументы
 ui_type loc4 = sum< ui_type, , ui_type >( ch, ui );
 Встречаются ситуации, когда невозможно вывести аргументы шаблона в контексте, где конкретизируется шаблон функции; следовательно, необходимо их явно задать. Именно выявление таких ситуаций и необходимость решить проблему послужила причиной поддержки явного задания аргументов шаблона в стандартном C++.
 В следующем примере берется адрес конкретизированной функции sum() и передается в качестве аргумента перегруженной функции manipulate(). Как мы показали в разделе 10.2, невозможно понять, как именно нужно конкретизировать sum(), если есть только списки параметров функций manipulate(). Имеется две разных функции sum(), и обе удовлетворяют условиям вызова. Следовательно, вызов manipulate() неоднозначен. Одним из способов разрешения такой неоднозначности является явное приведение типов. Однако лучше использовать явное задание аргументов шаблона: оно позволяет указать, как именно конкретизировать sum(), и, следовательно, выбрать нужный вариант перегруженной функции manipulate(). Например:
 template
  T1 sum( T2 op1, T3 op2 ) { /* ... */ }
 

<< Пред.           стр. 46 (из 121)           След. >>

Список литературы по разделу