Главная страница

Язык программирования C++. Вводный курс. С для начинающих


Скачать 5.41 Mb.
НазваниеС для начинающих
Дата24.08.2022
Размер5.41 Mb.
Формат файлаpdf
Имя файлаЯзык программирования C++. Вводный курс.pdf
ТипДокументы
#652350
страница45 из 93
1   ...   41   42   43   44   45   46   47   48   ...   93
477
}
Функция func() перегружена и тип ее параметра не позволяет однозначно определить ни аргумент шаблона Type, ни значение аргумента шаблона size. Результатом конкретизации вызова func() может быть любая из следующих функций: min( double (*)(double(&)[20]) )
Поскольку однозначно определить аргументы функции func() нельзя, взятие адреса конкретизированного шаблона в таком контексте приводит к ошибке компиляции.
Этого можно избежать, если использовать явное приведение типов для указания типа аргумента:
}
Лучше, однако, применять явное задание аргументов шаблона, как будет показано в разделе 10.4.
10.3.
Вывод аргументов шаблона А
При вызове шаблона функции типы и значения его аргументов определяются путем исследования типов фактических аргументов функции. Этот процесс называется выводом
аргументов шаблона.
Параметром функции в шаблоне min() является ссылка на массив элементов типа Type:
Type min( Type (&r_array)[size] ) { /* ... */ } 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 ); min( int (*)(int(&)[10]) ) int main() {
// правильно: с помощью явного приведения указывается тип аргумента func( static_cast< double(*)(rad) >(&min) ); template

С++ для начинающих
478
Для сопоставления с формальным параметром функции фактический аргумент также должен быть l-значением, представляющим тип массива. Следующий вызов ошибочен, так как pval имеет тип int*, а не является l-значением типа “массив int”.
}
При выводе аргументов шаблона не принимается во внимание тип значения, возвращаемого конкретизированным шаблоном функции. Например, если вызов min() записан так: int i1 = min( da ); то конкретизированный экземпляр min() имеет параметр типа “указатель на массив из восьми double” и возвращает значение типа double. Перед инициализацией i1 это значение приводится к типу int. Однако тот факт, что результат вызова min() используется для инициализации объекта типа int, не влияет на вывод аргументов шаблона.
Чтобы процесс такого вывода завершился успешно, тип фактического аргумента функции не обязательно должен совпадать с типом соответствующего формального параметра.
Допустимы три вида преобразований типа: трансформация l-значения, преобразование спецификаторов и приведение к базовому классу, конкретизированному из шаблона класса. Рассмотрим последовательно каждое из них.
Напомним, что трансформация l-значения – это либо преобразование l-значения в r- значение, либо преобразование массива в указатель, либо преобразование функции в указатель (все они рассматривались в разделе 9.3). Для иллюстрации влияния такой трансформации на вывод аргументов шаблона рассмотрим функцию min2() c одним параметром шаблона Type и двумя параметрами функции. Первый параметр min2() – это указатель на тип Type*. size теперь не является параметром шаблона, как в определении min(), вместо этого он стал параметром функции, а его значение должно быть явно передано при вызове:
} void f( int pval[9] ) {
// ошибка: Type (&)[] != int* int jval = min( pval ); double da[8] = { 10.3, 7.2, 14.0, 3.8, 25.7, 6.4, 5.5, 16.8 }; 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;

С++ для начинающих
479
min2()
можно вызвать, передав в качестве первого аргумента массив из четырех int, как в следующем примере:
}
Фактический аргумент функции ai имеет тип “массив из четырех int” и не совпадает с типом соответствующего формального параметра Type*. Однако, поскольку преобразование массива в указатель допустимо, то аргумент ai приводится к типу int* еще до вывода аргумента шаблона Type, для которого затем выводится тип int, и шаблон конкретизирует функцию min2(int*, int).
Преобразование спецификаторов добавляет const или volatile к указателям (такие трансформации также рассматривались в разделе 9.3). Для иллюстрации влияния преобразования спецификаторов на вывод аргументов шаблона рассмотрим min3() с первым параметром функции типа const Type*:
} min3()
можно вызвать, передав int* в качестве первого фактического аргумента, как в следующем примере: int i = min3( pi, 4 );
Фактический аргумент функции pi имеет тип “указатель на int” и не совпадает с типом формального параметра const
Type*
Однако, поскольку преобразование спецификаторов допустимо, то он приводится к типу const int* еще до вывода аргумента шаблона Type, для которого затем выводится тип int, и шаблон конкретизирует функцию min3(const int*, int).
Теперь обратимся к преобразованию в базовый класс, конкретизированный из шаблона класса. Вывод аргументов шаблона можно выполнить, если тип формального параметра функции является таким шаблоном, а фактический аргумент – базовый класс, конкретизированный из него. Чтобы проиллюстрировать такое преобразование, рассмотрим новый шаблон функции min4() с параметром типа Array&, где
Array
– это шаблон класса, определенный в разделе 2.5. (В главе 16 шаблоны классов обсуждаются во всех деталях.) int ai[4] = { 12, 8, 73, 45 }; int main() { int size = sizeof (ai) / sizeof (ai[0]);
// правильно: преобразование массива в указатель min2( ai, size ); template
// первый параметр имеет тип const Type*
Type min3( const Type* array, int size ) {
// ... int *pi = &ai;
// правильно: приведение спецификаторов к типу const int*

С++ для начинающих
480
} min4()
можно вызвать, передав в качестве первого аргумента ArrayRC, как показано в следующем примере. (ArrayRC – это шаблон класса, также определенный в главе 2; наследование классов подробно рассматривается в главах 17 и 18.)
}
Фактический аргумент ia_rc имеет тип ArrayRC. Он не совпадает с типом формального параметра Array&. Но одним из базовых классов для ArrayRC является Array, так как он конкретизирован из шаблона класса, указанного в качестве формального параметра функции. Поскольку фактический аргумент является производным классом, то его можно использовать при выводе аргументов шаблона.
Таким образом, перед выводом аргумент функции ArrayRC преобразуется в тип
Array
, после чего для аргумента шаблона Type выводится тип int и конкретизируется функция min4(Array&).
В процессе вывода одного аргумента шаблона могут принимать участие несколько аргументов функции. Если параметр шаблона встречается в списке параметров функции более одного раза, то каждый выведенный тип должен точно соответствовать типу, выведенному для того же аргумента шаблона в первый раз:
}
Оба фактических аргумента функции должны иметь один и тот же тип: либо int, либо unsigned int
, поскольку в шаблоне они принадлежат к одному типу T. Аргумент 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; template class ArrayRC : public Array { /* ... */ }; int main() {
ArrayRC ia_rc(10); min4( ia_rc ); 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 );

С++ для начинающих
481
шаблона T, выведенный из первого аргумента функции, – это int. Аргумент же шаблона
T
, выведенный из второго аргумента функции, – это unsigned int. Поскольку они оказались разными, процесс вывода завершается неудачей и при конкретизации шаблона выдается сообщение об ошибке. (Избежать ее можно, если явно задать аргументы шаблона при вызове функции min5(). В разделе 10.4 мы увидим, как это делается.)
Ограничение на допустимые типы преобразований относится только к тем фактическим параметрам функции, которые принимают участие в выводе аргументов шаблона. К остальным аргументам могут применяться любые трансформации. В следующем шаблоне функции sum() есть два формальных параметра. Фактический аргумент op1 для первого параметра участвует в выводе аргумента Type шаблона, а второй фактический аргумент op2
– нет.
Type sum( Type op1, int op2 ) { /* ... */ }
Поэтому при конкретизации шаблона функции sum() его можно подвергать любым трансформациям. (Преобразования типов, применимые к фактическим аргументам функции, описываются в разделе 9.3.) Например:
}
Тип второго фактического аргумента функции dd не соответствует типу формального параметра int. Но это не мешает конкретизировать шаблон функции sum(), поскольку тип второго аргумента фиксирован и не зависит от параметров шаблона. Для этого вызова конкретизируется функция sum(int,int). Аргумент dd приводится к типу int с помощью преобразования целого типа в тип с плавающей точкой.
Таким образом, общий алгоритм вывода аргументов шаблона можно сформулировать следующим образом:
1. По очереди исследуется каждый фактический аргумент функции, чтобы выяснить, присутствует ли в соответствующем формальном параметре какой-нибудь параметр шаблона.
2. Если параметр шаблона найден, то путем анализа типа фактического аргумента выводится соответствующий аргумент шаблона.
3. Тип фактического аргумента функции не обязан точно соответствовать типу формального параметра. Для приведения типов могут быть применены следующие преобразования:

трансформации l-значения

преобразования спецификаторов template int ai[] = { ... }; double dd; int main() {
// конкретизируется sum( int, int ) sum( ai[0], dd );

С++ для начинающих
482

приведение производного класса к базовому при условии, что формальный параметр функции имеет вид T& или T*, где список аргументов args содержит хотя бы один параметр шаблона.
4. Если один и тот же параметр шаблона найден в нескольких формальных параметрах функций, то аргумент шаблона, выведенный по каждому из соответствующих фактических аргументов, должен быть одним и тем же.
Упражнение 10.4
Назовите два типа преобразований, которые можно применять к фактическим аргументам функций, участвующим в процессе вывода аргументов шаблона.
Упражнение 10.5
Пусть даны следующие определения шаблонов:
Type min5( Type p1, Type p2 ) { /* ... */ }
Какие из приведенных ниже вызовов ошибочны? Почему?
(c) min3( ai, cobj1 );
10.4.
Явное задание аргументов шаблона A
В некоторых ситуациях автоматически вывести типы аргументов шаблона невозможно.
Как мы видели на примере шаблона функции min5(), если процесс вывода дает два различных типа для одного и того же параметра шаблона, то компилятор сообщает об ошибке – неудачном выводе аргументов.
В таких ситуациях приходится подавлять механизм вывода и задавать аргументы явно, указывая их с помощью заключенного в угловые скобки списка разделенных запятыми значений, который следует после имени конкретизируемого шаблона функции.
Например, если мы хотим задать тип unsigned int в качестве значения аргумента шаблона T в рассмотренном выше примере использования min5(), то нужно записать вызов конкретизируемого шаблона так: min5< unsigned int >( ui, 1024 ); template
Type min3( const Type* array, int size ) { /* ... */ } template 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 );
// конкретизируется min5( unsigned int, unsigned int )

С++ для начинающих
483
В этом случае список аргументов шаблона явно задает их типы.
Поскольку аргумент шаблона теперь известен, вызов функции больше не приводит к ошибке.
Обратите внимание, что при вызове функции min5() второй аргумент равен 1024, т.е. имеет тип int. Так как тип второго формального параметра функции при явном задании аргумента шаблона установлен в unsigned int, то второй фактический параметр функции приводится к типу unsigned int с помощью стандартного преобразования целых типов.
В предыдущем разделе мы говорили, что в процессе вывода аргументов шаблона к фактическим аргументам функции разрешается применять только ограниченное множество преобразований типов. Трансформация int в unsigned int в это множество не входит. Но если аргументы шаблона задаются явно, выполнять вывод типов не нужно, поскольку они уже зафиксированы. Следовательно, при явном задании аргументов шаблона для приведения типов фактических аргументов функции к типам формальных параметров можно применять любые стандартные преобразования.
Помимо разрешения любых преобразований фактических аргументов функции, явное задание аргументов шаблона помогает избежать и других проблем, встающих перед программистом. Рассмотрим следующую задачу. Мы хотим определить шаблон функции с именем sum() так, чтобы его конкретизация возвращала значения типа, достаточно большого для представления суммы двух значений любых двух типов, переданных в любом порядке. Как это сделать? Какой тип возвращаемого значения следует задать?
??? sum( T, U );
В нашем случае нельзя использовать ни тот, ни другой параметрический тип, иначе мы неизбежно допустим ошибку: sum( ui, ch ); // правильно: T sum( T, U );
Решение заключается в том, чтобы ввести в шаблон третий параметр для обозначения типа возвращаемого значения:
T1 sum( T2, T3 );
Поскольку тип возвращаемого значения может отличаться от типов аргументов функции,
T1
не упоминается в списке формальных параметров. Это потенциальная проблема, так как тип T1 не может быть выведен из фактических аргументов функции. Однако, если
// каким должен быть тип возвращаемого значения: T или U template char ch; unsigned int ui;
// ни T, ни U нельзя использовать в качестве типа возвращаемого значения sum( ch, ui ); // правильно: U sum( T, U );
// T1 не появляется в списке параметров шаблона функции template

С++ для начинающих
484
при конкретизации sum() мы зададим аргументы шаблона явно, то избегнем сообщения компилятора о невозможности вывести T1. Например:
}
Не хватает возможности явно задать T1, но не T2 и T3, поскольку их можно вывести из аргументов функции при вызове.
При явном задании аргументов шаблона необходимо перечислять только те, которые не могут быть выведены автоматически. Но, как и в случае аргументов функции со значениями по умолчанию, опускать можно исключительно “хвостовые”: ui_type loc4 = sum< ui_type, , ui_type >( ch, ui );
Встречаются ситуации, когда невозможно вывести аргументы шаблона в контексте, где конкретизируется шаблон функции; следовательно, необходимо их явно задать. Именно выявление таких ситуаций и необходимость решить проблему послужила причиной поддержки явного задания аргументов шаблона в стандартном C++.
В следующем примере берется адрес конкретизированной функции sum() и передается в качестве аргумента перегруженной функции manipulate(). Как мы показали в разделе
10.2, невозможно понять, как именно нужно конкретизировать sum(), если есть только списки параметров функций manipulate(). Имеется две разных функции sum(), и обе удовлетворяют условиям вызова. Следовательно, вызов manipulate() неоднозначен.
Одним из способов разрешения такой неоднозначности является явное приведение типов.
Однако лучше использовать явное задание аргументов шаблона: оно позволяет указать, как именно конкретизировать sum(), и, следовательно, выбрать нужный вариант перегруженной функции manipulate(). Например: 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 );
// правильно: 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 >;
// ошибка: опускать можно только “хвостовые” аргументы

С++ для начинающих
485
}
Отметим, что явное задание аргументов шаблона следует использовать только тогда, когда это абсолютно необходимо для разрешения неоднозначности или для конкретизации шаблона функции в контексте, где вывести аргументы невозможно. Во- первых, определение типов и значений аргументов шаблона проще оставить компилятору. А во-вторых, если мы модифицируем объявления в программе, так что типы аргументов функции при вызове конкретизированного шаблона изменятся, то компилятор автоматически скорректирует вызов без нашего вмешательства. С другой стороны, если аргументы шаблона заданы явно, необходимо проверить, что они по- прежнему отвечают новым типам аргументов функции. Поэтому мы рекомендуем избегать явного задания аргументов шаблона.
Упражнение 10.6
Назовите две ситуации, когда использование явного задания аргументов шаблона необходимо.
Упражнение 10.7
Пусть дано следующее определение шаблона функции sum():
T1 sum( T2, T3 );
Какие из приведенных ниже вызовов ошибочны? Почему?
(d) sum( fobj2, dobj2 ); template
T1 sum( T2 op1, T3 op2 ) { /* ... */ } void manipulate( int (*pf)( int,char ) ); void manipulate( double (*pf)( float,float ) ); int main()
{
// ошибка: какой из возможных экземпляров sum:
// int sum( int,char ) или double sum( float, float )? manipulate( &sum );
// берется адрес конкретизированного экземпляра
// double sum( float, float )
// вызывается: void manipulate( double (*pf)( float, float ) ); manipulate( &sum< double, float, float > ); template double dobj1, dobj2; float fobj1, fobj2; char cobj1, cobj2;
(a) sum( dobj1, dobj2 );
(b) sum( fobj1, fobj2 );
(c) sum( cobj1, cobj2 );

С++ для начинающих
1   ...   41   42   43   44   45   46   47   48   ...   93


написать администратору сайта