С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 81
Текст из файла (страница 81)
Однако тот факт, что результат вызова min()используется для инициализации объекта типа int, не влияет на вывод аргументовшаблона.Чтобы процесс такого вывода завершился успешно, тип фактического аргумента функциине обязательно должен совпадать с типом соответствующего формального параметра.Допустимы три вида преобразований типа: трансформация l-значения, преобразованиеспецификаторов и приведение к базовому классу, конкретизированному из шаблонакласса. Рассмотрим последовательно каждое из них.Напомним, что трансформация l-значения – это либо преобразование l-значения в rзначение, либо преобразование массива в указатель, либо преобразование функции вуказатель (все они рассматривались в разделе 9.3).
Для иллюстрации влияния такойтрансформации на вывод аргументов шаблона рассмотрим функцию min2() c однимпараметром шаблона Type и двумя параметрами функции. Первый параметр min2() –это указатель на тип Type*. size теперь не является параметром шаблона, как вопределении min(), вместо этого он стал параметром функции, а его значение должноtemplate <class Type>// первый параметр имеет тип 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;быть явно передано при вызове:}478С++ для начинающих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() сtemplate <class Type>// первый параметр имеет тип const Type*Type min3( const Type* array, int size ) {// ...первым параметром функции типа const Type*:}min3() можно вызвать, передав int* в качестве первого фактического аргумента, как вint *pi = &ai;// правильно: приведение спецификаторов к типу const int*следующем примере:int i = min3( pi, 4 );Фактический аргумент функции pi имеет тип “указатель на int” и не совпадает с типомформального параметра constType*.
Однако, поскольку преобразованиеспецификаторов допустимо, то он приводится к типу const int* еще до выводааргумента шаблона Type, для которого затем выводится тип int, и шаблонконкретизирует функцию min3(const int*, int).Теперь обратимся к преобразованию в базовый класс, конкретизированный из шаблонакласса. Вывод аргументов шаблона можно выполнить, если тип формального параметрафункции является таким шаблоном, а фактический аргумент – базовый класс,конкретизированный из него. Чтобы проиллюстрировать такое преобразование,рассмотрим новый шаблон функции min4() с параметром типа Array<Type>&, гдеArray – это шаблон класса, определенный в разделе 2.5. (В главе 16 шаблоны классовобсуждаются во всех деталях.)479С++ для начинающихtemplate <class Type>class Array { /* ... */ }template <class Type>Type min4( Array<Type>& 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<int>, какпоказано в следующем примере.
(ArrayRC – это шаблон класса, также определенный вtemplate <class Type>class ArrayRC : public Array<Type> { /* ... */ };int main() {ArrayRC<int> ia_rc(10);min4( ia_rc );главе 2; наследование классов подробно рассматривается в главах 17 и 18.)}Фактический аргумент ia_rc имеет тип ArrayRC<int>. Он не совпадает с типомформального параметра Array<Type>&. Но одним из базовых классов для ArrayRC<int>является Array<int>, так как он конкретизирован из шаблона класса, указанного вкачестве формального параметра функции. Поскольку фактический аргумент являетсяпроизводным классом, то его можно использовать при выводе аргументов шаблона.Таким образом, перед выводом аргумент функции ArrayRC<int> преобразуется в типArray<int>, после чего для аргумента шаблона Type выводится тип int иконкретизируется функция min4(Array<int>&).В процессе вывода одного аргумента шаблона могут принимать участие несколькоаргументов функции.
Если параметр шаблона встречается в списке параметров функцииболее одного раза, то каждый выведенный тип должен точно соответствовать типу,template <class T> 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.
Аргумент480С++ для начинающихшаблона T, выведенный из первого аргумента функции, – это int. Аргумент же шаблонаT, выведенный из второго аргумента функции, – это unsigned int. Поскольку ониоказались разными, процесс вывода завершается неудачей и при конкретизации шаблонавыдается сообщение об ошибке. (Избежать ее можно, если явно задать аргументышаблона при вызове функции min5(). В разделе 10.4 мы увидим, как это делается.)Ограничение на допустимые типы преобразований относится только к тем фактическимпараметрам функции, которые принимают участие в выводе аргументов шаблона. Костальным аргументам могут применяться любые трансформации. В следующем шаблонефункции sum() есть два формальных параметра.
Фактический аргумент op1 для первогопараметра участвует в выводе аргумента Type шаблона, а второй фактический аргументtemplate <class Type>op2 – нет.Type sum( Type op1, int op2 ) { /* ... */ }Поэтому при конкретизации шаблона функции sum() его можно подвергать любымтрансформациям. (Преобразования типов, применимые к фактическим аргументамint ai[] = { ... };double dd;int main() {// конкретизируется sum( int, int )sum( ai[0], dd );функции, описываются в разделе 9.3.) Например:}Тип второго фактического аргумента функции dd не соответствует типу формальногопараметра int.
Но это не мешает конкретизировать шаблон функции sum(), посколькутип второго аргумента фиксирован и не зависит от параметров шаблона. Для этоговызова конкретизируется функция sum(int,int). Аргумент dd приводится к типу int спомощью преобразования целого типа в тип с плавающей точкой.Таким образом, общий алгоритм вывода аргументов шаблона можно сформулироватьследующим образом:1. По очереди исследуется каждый фактический аргумент функции, чтобы выяснить,присутствует ли в соответствующем формальном параметре какой-нибудь параметршаблона.2. Если параметр шаблона найден, то путем анализа типа фактического аргументавыводится соответствующий аргумент шаблона.3.
Тип фактического аргумента функции не обязан точно соответствовать типуформального параметра. Для приведения типов могут быть применены следующиепреобразования:•трансформации l-значения•преобразования спецификаторов481С++ для начинающих•приведение производного класса к базовому при условии, что формальныйпараметр функции имеет вид T<args>& или T<args>*, где список аргументовargs содержит хотя бы один параметр шаблона.4. Если один и тот же параметр шаблона найден в нескольких формальных параметрахфункций, то аргумент шаблона, выведенный по каждому из соответствующихфактических аргументов, должен быть одним и тем же.Упражнение 10.4Назовите два типа преобразований, которые можно применять к фактическимаргументам функций, участвующим в процессе вывода аргументов шаблона.Упражнение 10.5template <class Type>Type min3( const Type* array, int size ) { /* ...
*/ }template <class Type>Пусть даны следующие определения шаблонов: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 );482С++ для начинающих483В этом случае список аргументов шаблона <unsigned int> явно задает их типы.Поскольку аргумент шаблона теперь известен, вызов функции больше не приводит кошибке.Обратите внимание, что при вызове функции min5() второй аргумент равен 1024, т.е.имеет тип int.