С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 86
Текст из файла (страница 86)
Рассмотрим пример, показывающий, зачем может потребоваться объявитьобычную функцию.Предположим, что мы хотим определить специализацию шаблона функцииmin<int>(int,int). Нужно, чтобы именно эта функция вызывалась при обращении кmin() с аргументами любых целых типов, пусть даже неодинаковых. Из-за ограничений,наложенных на преобразования типов, при передаче фактических аргументов разныхтипов функция min<int>(int,int) не будет конкретизирована из шаблона.
Мы моглибы заставить компилятор выполнить конкретизацию, явно задав аргументы шаблона,однако решение, при котором не требуется модифицировать каждый вызов,предпочтительнее. Определив обычную функцию, мы добьемся того, что программабудет вызывать специальную версию min(int,int) для любых фактических аргументовцелых типов без явного указания аргументов шаблона:502С++ для начинающих503// определение шаблона функцииtemplate <class Type>Type min( Type t1, Type t2 ) { ... }int ai[4] = { 22, 33, 44, 55 };short ss = 88;void call_instantiation() {// ошибка: для этого вызова нет функции-кандидатаmin( ai[0], ss );}// обычная функцияint min( int a1, int a2 ) {min<int>( a1, a2 );}int main() {call_instantiation() {// вызывается обычная функцияmin( ai[0], ss );}Для вызова min(ai[0],ss) из call_instantiation нет ни одной функции-кандидата.Попытка сгенерировать ее из шаблона min() провалится, поскольку для аргументашаблона Type из фактических аргументов функции выводятся два разных значения.Следовательно, такой вызов ошибочен.
Однако при обращении к min(ai[0],ss) внутриmain() видимо объявление обычной функции min(int, int). Тип первогофактического аргумента этой функции точно соответствует типу формального параметра,а второй аргумент может быть преобразован в тип формального параметра с помощьюрасширения типа. Поскольку для второго вызова устояла только данная функция, то онаи вызывается.Разобравшись с разрешением перегрузки функций, конкретизированных из шаблонов,специализацией шаблонов функций и обычных функций с тем же именем, подытожимвсе, что мы об этом рассказали:1. Построить множество функций-кандидатов.Рассматриваются шаблоны функций с тем же именем, что и вызванная. Еслиаргументы шаблона выведены из фактических аргументов функции успешно, то вмножество функций-кандидатов включается либо конкретизированный шаблон, либоспециализация шаблона для выведенных аргументов, если она существует.2.
Построить множество устоявших функций (см. раздел 9.3).В множестве функций-кандидатов остаются только функции, которые можновызвать с данными фактическими аргументами.3. Ранжировать преобразования типов (см. раздел 9.3).a. Если есть только одна функция, вызвать именно ее.b. Если вызов неоднозначен, удалитьконкретизированные из шаблонов.измножестваустоявшихфункции,4. Разрешить перегрузку, рассматривая среди всех устоявших только обычные функции(см. раздел 9.3).a. Если есть только одна функция, вызвать именно ее.С++ для начинающихb. В противном случае вызов неоднозначен.Проиллюстрируем эти шаги на примере. Предположим, есть два объявления – шаблонаtemplate <class Type>Type max( Type, Type ) { ...
}// обычная функцияфункции и обычной функции. Оба принимают аргументы типа double:double max( double, double );А вот три вызова max(). Можете ли вы сказать, какая функция будет вызвана в каждомint main() {int ival;double dval;float fd;// ival, dval и fd присваиваются значенияmax( 0, ival );max( 0.25, dval );max( 0, fd );случае?}Рассмотрим последовательно все три вызова:1. max(0,ival). Оба аргумента имеют тип int. Для вызова есть два кандидата:конкретизированная из шаблона функция max(int, int) и обычная функцияmax(double, double).
Конкретизированная функция точно соответствуетфактическим аргументам, поэтому она и вызывается;2. max(0.25,double). Оба аргумента имеют тип double. Для вызова есть двакандидата: конкретизированная из шаблона max(double, double) и обычнаяmax(double, double). Вызов неоднозначен, поскольку точно соответствует обеимфункциям. Правило 3b говорит, что в таком случае выбирается обычная функция;.3. max(0,fd). Аргументы имеют тип int и float соответственно. Для вызовасуществует только один кандидат: обычная функция max(double, double).
Выводаргументов шаблона заканчивается неудачей, так как значения типа Type,выведенные из разных фактических аргументов функции, различны. Поэтому вмножество кандидатов конкретизированная из шаблона функция не попадает.Обычная же функция устояла, поскольку существуют преобразования типовфактических аргументов в типы формальных параметров; она и выбирается. Если быобычная функция не была объявлена, вызов закончился бы ошибкой.А если бы мы определили еще одну обычную функцию для max()? Например:504С++ для начинающихtemplate <class T> T max( T, T ) { ... }// две обычные функцииchar max( char, char );double max( double, double );int main() {float fd;// в пользу какой функции разрешается вызов?max( 0, fd );Будет ли в таком случае третий вызов разрешен по-другому? Да.}Правило 3b говорит, что, поскольку вызов неоднозначен, следует рассматривать толькообычные функции.
Ни одна из них не считается наилучшей из устоявших, так какпреобразования типов фактических аргументов одинаково плохи: в обоих случаях дляустановления соответствия требуется стандартная трансформация. Таким образом, вызовнеоднозначен, и компилятор сообщает об ошибке.Упражнение 10.11template <class Type>Type max( Type, Type ) { ... }Вернемся к представленному ранее примеру:int main() {int ival;double dval;float fd;max( 0, ival );max( 0.25, dval );max( 0, fd );double max( double, double );}Добавим в множество объявлений в глобальной области видимости следующуюспециализацию шаблона функции:template <> char max<char>* char, char ) { ...
}Составьте список кандидатов и устоявших функций для каждого вызова max() внутриmain().Предположим, что в main() добавлен следующий вызов:505С++ для начинающих506int main() {// ...max( 0, 'j' );}В пользу какой функции он будет разрешен? Почему?Упражнение 10.12Предположим, что есть следующее множество определений и специализаций шаблонов, аint i;char str[24];unsigned int ui;int ia[24];template <class T> T calc( T*, int );template <class T> T calc( T, T );template<> chat calc( char*.
int );также объявления переменных и функций:double calc( double, double );Выясните, какая функция или конкретизированный шаблон вызывается в каждом изпоказанных ниже случаев. Для каждого вызова перечислите функции-кандидаты и(a) cslc( str, 24 );(b) calc( is, 24 );(d) calc( i, ui );(e) calc( ia, ui );устоявшие функции; объясните, какая из устоявших функций будет наилучшей.(c) calc( ia[0], 1 );(f) calc( &i, i );10.9. Разрешение имен в определениях шаблонов АВнутри определения шаблона смысл некоторых конструкций может различаться взависимости от конкретизации, тогда как смысл других всегда остается неизменным.template <typename Type>Type min( 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];print( "Minimum value found: ");print( min_val );return min_val;Главную роль играет наличие в конструкции формального параметра шаблона:}В функции min() типы переменных array и min_val зависят от фактического типа,которым будет заменен Type при конкретизации шаблона, тогда как тип переменнойС++ для начинающихsize останется int при любом типе параметра шаблона.
Следовательно, типы array иmin_val в разных конкретизациях различны. Поэтому мы говорим, что типы этихпеременных зависят от параметра шаблона, тогда как тип size от него не зависит.Так как тип min_val неизвестен, то неизвестна и операция, которая будет использоватьсяпри появлении min_val в выражении. Например, какая функция print() будет вызванапри обращении print(min_val)? С типом аргумента int? Или float? Будет ли вызовошибочным, поскольку не существует функции, которая может быть вызвана саргументом того же типа, что и min_val? Принимая все это во внимание, мы говорим,что и вызов print(min_val) зависит от параметра шаблона.Такие вопросы не возникают для тех конструкций внутри min(), которые не зависят отпараметров шаблона. Например, всегда известно, какая функция должна быть вызванадля print( "Minimum value found: ").
Это функция печати строк символов. Вданном случае print() остается одной и той же при любой конкретизации шаблона, тоесть не зависит от его параметров.В главе 7 мы видели, что в C++ функция должна быть объявлена до ее вызова. Нужно лиобъявлять функцию, вызываемую внутри шаблона, до того, как компилятор увидит егоопределение? Должны ли мы объявить функцию print() в предыдущем примере доопределения шаблона min()? Ответ зависит от особенностей имени, на которое мыссылаемся. Конструкцию, не зависящую от параметров шаблона, следует объявить передее использованием в шаблоне. Представленное выше определение шаблона функцииmin() некорректно.
Поскольку вызовprint( "Minimum value found: ");не зависит от параметров шаблона, то функция print() для печати строк символовдолжна быть объявлена до использования. Чтобы исправить эту ошибку, можно// ---- primer.h ---// это объявление необходимо:// внутри min() вызывается print( const char * )void print( const char * );template <typename Type>Type min( Type* array, int size ) {// ...print( "Minimum value found: ");print( min_val );return min_val;поместить объявление print() перед определением min():}С другой стороны, объявление функции print(), используемой для печати min_val,пока не нужно, так как еще неизвестно, какую конкретно функцию надо искать. Мы незнаем, какая функция print() будет вызвана при обращении print(min_val), пока типmin_val не станет известным.507С++ для начинающихКогда же должна быть объявлена функция print(), вызываемая при обращении#include <primer.h>void print( int );int ai[4] = {12, 8, 73, 45 };int main() {int size = sizeof(ai) / sizeof(int);// конкретизируется min( int*, int )min( &ai[0], size );print(min_val)? До конкретизации шаблона.