Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 87
Текст из файла (страница 87)
Даже яри вошвй попытке сделать вто компилятор ОтВЕрГНЕт СгвнврирОВаННуЮ функциЮ чв С С ог<П1оЬ>: г я от с ( ) я. 15.б. Шаблоны функций Данное средство введено из-за необходимости иметь функции-члены в шаблонах классов, а также потому, что сама концепция шаблонов без него выглядела незаконченной. Конечно, были еше и хрестоматийные примеры, вроде функции ьог с ( ) . Эндрю Кениг и /(лекс Степанов предложили много примеров, доказываюших необходимость шаблонов функций.
Самым важным стоит считать пример сортировки массива: // объявление шаблона функции: сешр1асе<с1аяя Т> чо1б вогс(чессог<Т>ь); чофб 1(чессог<1пс>а ч1, чессог<бсг1па>а чь) ( яогс(ч1); // яогс(чессог<ьпс>ь ч); ьогс (чя); // ьогс (чессог<зсгфлд>ь и) / ) // определение шаблона функции Сешр1аге<с1аяя Т> чо1б яогг(чесгог<Т>Ь и) /* Отсортировать элементы в порядке возрастания Алгоритм: пузырьковая сортировка (неэффективный, но очевидный) */ ( ппя1чпеб (пг и = н.в1зе(); 1ог (1пс 1 = сш 1<п-1; 1++) 1ог (1пс 1=п-1г 1<1; 1-") НИИИ ИИВ Шаблоны Как и ожидалось, шаблоны функций оказались незаменимы для поддержки шаблонов классов, когда сервисы предоставлялись обычными функциями, а не функциями-членами снапример дружественными функциями, см.
раздел 3.6.1). Далее рассматриваются детали реализации шаблонов функций. 15.6.1. Выведение аргументов шаблона функции Для шаблонов функций не нужно задавать аргументы шаблона — компилятор сам выводит их по фактическим параметрам, переданным при вызове. Разумеется, каждый аргумент шаблона, не специфицированный явно (см. раздел 15.6.2), должен однозначно определяться исходя из фактического параметра. В ходе стандартизации стало ясно, что необходимо точно определить, насколько «умно> должен вести себя компилятор при выведении аргументов шаблона из фактических параметров функции. Например, допустимо ли следуюп(ее: Сепр1аСе<с1аяя Т, Тпг 1> т 1оохпр(впсгег<т,1>а ь, сопяс сьаг* р)> тпс Е(Виггег<1пс, 128>а Ьпт, сопяС спас* р) ( геппгп 1оокпр(Ьпс,р)3 // использовать 1оохпр(), где // Т - это ).пС, а 1 — 128 ) Ранее ответ на вопрос был отрицательным, поскольку аргументы, не являющиеся типами, нельзя было вывести.
Это означает, что невозможно определить не являющуюся членом невстраиваемую функцию, которая применялась бы к шаблону класса, принимающего в качестве аргумента не-тип. Например: севр1асе<с1аяя т, 1пс 1> с1авя Впссег ( сг1епс) Т 1оо)спр(впсгега, сопяС сЬаг*); // ); Здесь требуется функция, определение которой раньше было незаконно. После пересмотра данного предложения перечень конструкций, допустимых в списке аргументов шаблона функции, стал выглядеть так: т сопяс т чо1аС11е Т т* ть Т[п) 11 (ч[3) < ч(3-1]) ( // переставить местами н[3) и н[3-1) т Семр = ч[3)' ч[3] = ч[3 1]! н[3-1] = Семр; ИИИИИИКИ Шаблоны функций ваше суре(1] ст<т> Ст<1> т (*)(агдв) вове суре (*) вове суре (*) т с::* с т::* (агдв сопоа!п!пд т) (агдв сопла!и!пд 1) сешр1асе<с1авв т, с1авв 0> уо!б г(сопев т*, 0(*) (щ ); !пг д(!пс); уо!6 'п(попас сваг* р) ( г(р д) ' г(р,ы; ) // т — зто с)заг, П вЂ” зто !пс // ошибка: невозможно внвести 0 Глядя на фактические параметры в первом вызове й ( ), мы легко можем вывестн фактические аргументы шаблона.
Смотря на второй вызов й (), видим, что )т ( ) не соответствует образцу () (*) (()), поскольку типы аргумента и возвращаемого значения различаются. В прояснении этого и многих других подобных вопросов существенную помощь оказал Джон Спайсер ('.)оЬп Яр(сег). '5.Б.2. Задание аргументов шаблона функции Проектируя шаблоны, я думал о том, чтобы разрешить явное задание аргументов для шаблона функции точно так же, как можно задавать аргументы шаблонов классов. Например: уессог<!пс> у(10)/ // класс, аргумент шаблона '!пс' вовс<!пг>(л); // Функция, аргумент шаблона '!пс' Однако от этой идеи пришлось отказаться, потому что в большинстве примеров явно задавать аргументы шаблона не требовалось.
Также, я опасался неоднозначностей и трудностей при синтаксическом анализе. Скажем, как следует разбирать этот пример? уоьд д() ( 0<1>(0) / // (!) < (1>(0) ) или (!<1>] (О) т ) Здесь т — аргумент-тип шаблона, ! — аргумент шаблона, который не является типом, ст — имя ранее объявленного шаблона класса, алдв сопсайпйпд т— список аргументов, из которого можно определить Т, применяя зги правила, а С вЂ” имя класса. Теперь пример с функцией 1ооКпр ( ) становится корректным. Пользователям не надо заучивать этот перечень, так как он просто формализует очевидный синтаксис. Вот другой пример: Шаблоны БИИИИИИВ Теперь я не считаю это проблемой. Если й — имя шаблона, то б< — начало квалифицированного имени и последующие лексемы должны интерпретироваться с учетом этого факта; в противном случае < означает «меньше».
Явное залание может быть полезно, поскольку мы не можем вывести тип возвращаемого значения по вызову шаблона функции: Гешр1асе<с1азз Т, с1азз ()> Т сопчегг(П и) ( геспгп и; ) чоьб С(гпс 1) ( сопчегс(1); // ошибка: нельзя вывести Т сопчегг<бопые>(1); // Т - поп)>1е, и - 1пг сопчегг<с)заг,боп)>1е>(1); // т - с)заг, и - боп)>1е сопчегс<с)тат*,боп)>1е>(1); // Т вЂ” с)заг*, П вЂ” боп)>1е // ошибка: нельзя преобразовать // боп)>1е в с)зал* ) Как н для аргументов функции по умолчанию, в списке явно заданных аргументов шаблона можно опускать только крайние правые элементы. Явное задание аргументов шаблона позволяет определить семейства функций преобразования и создания объектов.
Явное преобразование, которое выполняет то, чего можно добиться одним лишь неявным преобразованием, например функция сопчегс () в приведенном примере, достаточно часто требуется н хорошо подходит для включения в библиотеку. Еще один вариант — применить проверку, которая гарантировала бы, что для любого сужающего преобразования можно будет обнаружить ошибку во время выполнения.
Мы осознанно сделали похожим синтаксис новых операторов приведения (см. раздел 14.3) и явно квалифицированных вызовов шаблона функции. С помощью новых операторов приведения выражаются действия, которые нельзя описать другими средствами языка. Аналогичные операции, например сопчегг (), можно выразить в виде шаблонов функций, поэтому они необязательно должны быть встроенными операторами. Еще одно применение явно заданных аргументов шаблона функции — управление работой алгоритма за счет задания типа или значения локальной переменной. Например: Гешр1асе<с1аза ТТ, с1азз йт> чо1с) г (ат а) ( ТТ Гешр = а; // используем ТТ для управления // точностью вычислений // чоьб о(актау<(1оас>а а) ( г<11оаг>(а); г<боп)>1е>(а); г<опад>(а); 2161ИИИФИИ Шаблоны функций 15.Б.З.
Перегрузка шаблона функции Коль скоро существуют шаблоны функций, встает вопрос, как следует разрешать их перегрузку. Для данного средства допустимы только точные соответствия, а при разрешении перегрузки предпочтение отдается обычной функции с тем же именем: «разрешение перегрузки для шаблонов функций и других функций стем же именем выполняется в три агапа (А((М]: о поиск точного соответствия [А((М, В13.2] среди функций; называние функции, ваги она найдена; о нахождение шаблона функции, из которого можно инстанцировать функцию, точно соответствующую параметрам вызова; вызов этой функции, если шаблон найден; ьз попытка применить обычную перегрузку (АВМ, В13.2] для функций; вызов функции, воли оиа найдена.
Если соответствие не найдено, вызов считается ошибкой. Если на первом шаге отыскивается более одного соответствия, то вызов неоднозначен и также считается ошибкой». Теперь такой подход кажется узкоспециализированным. Хотя он и работает, но служит почвой для многих мелких сюрпризов и неприятностей. Уже в то время мне было ясно, что лучше как-то унифицировать правила для обычных функций и шаблонов. Но я не знал как. Вот приблизительный вариант альтернативного подхода, сформулированный Эндрю Кенигом; «Для данного вызова найти множество функций, которые в принципе можно было бы подставить.
В стандартном случае оно будет содержать функции, сгенерированные из разных шаблонов. Применить обычные правила разрешения к этому множеству функций». Такое решение позволило бы применять преобразования к аргументам шаблонов функций, и мы получили бы общую схему перегрузки для любых функций. Например: сепр1все<с1авв т> с1авв в ( /* ... */ Сепр1аге<с1авя Т> с1авв Р : рп)>1!с В<Т> ( /* ... */ ); севр1асе<с1авв Т> уо!с) Г(В<Т>*)г уо!с) о(в<!пс>* рЫ О<!пс>* рс)) г(рЫ г // 1<!пс>(ры с(рс)); // с<!пс>((В<!пс>*)рс)) г // используется стандартное преобразование Это необходимо для того, чтобы шаблоны функций правильно взаимодействовали с наследованием.
Другой пример: Селгр1аСе<с1авв Т> Т пах(Т,Т); сопвс !пс в = тг Включение в Сч.ч- явного задания аргументов шаблона функции одобрено на заселании комитета в Сан-Хосе в ноябре 1993 г. ППИИИ>ИВ Шаблоны уоЫ )<() ( пах(я,7); // пах(!пл(а),7); используется тривиальное преобразование ) В АКМ я предвидел, что возникнет необходимость ослабить правило, запрещающее применение каких бы то ни было преобразований.
Многие из ныне существующих компиляторов допускают приведенные выше примеры. Но этот вопрос еще предстоит формально согласовать. 15.Б.З.1. Условные выражения в шаблонах При написании шаблона функции иногда желательно, чтобы определение могло зависеть от свойств аргумента шаблона. Так, в (5тгоцэ(гцр, 1988Ь) читаем: крассмотрим, как можно было бы написать функцию печати для типа ввктора, которая пврвд выдачей сортирувт элвмвнты, но лишь тогда, когда сортировка возможна. Хорошо бы иметь некоторое средства, которое выяснявт, можно ли к объектам данного типа применить данную апврацию, скгаквм, <. Напримвр: Сепр1аге<с1авэ т> уо!г) уеслог<т>ггрг!пг() ( // если в т есть операция <, отсортировать перед печатью !! (7тггорегалог<) аогл()г Гот (!лс 1=О; !<азг !++) ( /* ... */ ) ) уо!г) г(ь!ас1сег<!пг> 11, ь!ас1сег<!пс> 12, !лс* р1, !па* р2) ( теуегае(р1,р2)г геуегае(11,12); ) где 1,1эс1сегасог используется для доступа к элементам в некотором определенном пользователем контейнере, а с помощью !пс* можно получать доступ При пвчати вектора, элементы которого можно сравнивать, выэывавтся функция а осл ( ), в противном случае вв вызов пропускается».
Я решил не предоставлять такое средство для опроса типа, поскольку был убежден, — как убежден и сейчас, — что оно стало бы причиной написания плохо структурированного кода. В некоторых отношениях эта техника сочетает худшие черты макросов и чрезмерного использования КТТ1 !см. раздел 14.2.3).
Вместо этого для конкретных типов аргументов шаблона можно воспользоваться специализацией (см. раздел 15.10.3). Или же те операции, выполнение которых нельзя гарантировать для всех возможных типов аргументов, вынести в отдельные функции-члены, вызываемые лишь тогда, когда это возможно !см. раздел 15.5). Наконец, можно применить и перегрузку шаблонов функций, чтобы предоставить реализации для разных тинов. В качестве примера рассмотрим шаблон функции теуегэе ( ), которая изменяет порядок элементов в контейнере на противоположный, если ей переданы итераторы, идентифицирующие первый и последний элементы. Пользовательский код должен был бы вызывать сс так: Шаблоны функций ПИИИИИИЙ сеир1асе<с1авв 1тет> !п1!пе уо!с( теуетяе(1Сет 1!твт, 1тет 1аят) ( ген(1!тяС,1аят,1Сеттуре(1!тят)); ) Для выбора 1 с еттуре используется разрешение перегрузки: с1аяв Капг)оилссеяя ( ); Сеир1ате<с1аяя Т> !п1!пе Капг)оиьссевя 1СетТуре(Т*) ( тесптп касс(оилссеяв(); ) с1авя Готиатс( ( ); сеир1асе<с1аяя т> !п1!пе готиатс) 1сеттуре(ь!вс1сетасот<т>) ( теситп Ротиатс((); ) Здесь для !пс* будет выбран каис)оидссевв, а для 1,!вс1сет — тотиатс).