Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 37
Текст из файла (страница 37)
В конце концов может случиться так, что этот процесс удастся провести для другого шаблона с именем шах (шаблоны функций, как и обычные функции, можно перегружаттй см. раздел 2.4, стр. 37, и главу 12, "Специализация и перегрузка"). Даже если удалось вывести все параметры шаблона, это еше не означает, что вывод успешен. Бывает и так, что при подстановке выведенных аргументов в оставшуюся часть определения функции получается некорректная конструкция. Приведем пример такой ситуации. Сешр1апе<пурепаше Т> Сурепаше Т!:Е1ешепСТ ап(Т сопне* а, Тпк 1) ( тесилп а(1); ) тгоЫ й(йпс* р) ( 1пс х = ас(р, 7); ) При анвппе этого кода приходим к выводу, что вместо параметра типа Т нужно подставить тип 1пе * (в силу того, что параметр типа Т используется только в одном месте, никаких связанных с ним неоднозначностей возникать не должно). Однако подстановка Тпс* вместо Т в возвращаемый тип Т ! ! Е1ешепсТ явно недопустима в С++, поэтому вывод сдала!ъ не удается'.
Скорее всего, в этом случае в сообщении об ошибке будет указано, что не удалось найти определение функции ас ( ), соответствующее ее вызову в программе. Если же явно указаны все аргументы шаблона, то ситуация меняется. В таком случае можно прийти к однозначному заключению о том, какой именно из шаблонов функции вызывается (даже если этн шаблоны перегружены), поэтому вероятносп того, что удастся выполнить вывод аргумента для другого шаблона, равна нулю.
В этом случае в сообщении об ошибке, вероятно, будет отмечено, что неверно указаны аргументы шаблона функции ас ( ) . Это можно проверить на практике, сравнивая сообщения компилятора для предыдущего кода с теми, которые будут вьщаваться при компиляции, например, такого кода: тгоЫ х (Тпс* р] ( 1пс х = ас<йпс*>(р, 7) ) Рассмотрим, как происходит процедура проверки соответствия параметра и аргумента. Опишем его в терминах соответствия типа А (выведенного из типа аргумента) параметризованному типу Р.
Если параметр объявлен как ссылка, считаем, что Р— это тип . на который делается ссылка, а А — тип аргумента. В противном случае параметр имев~ ! В данном случае неуспешный вывод привел к ошибке. Однако в силу прннпнпа 8НХАЕ (шьвпплюп ГМ! Ь по! ап епог — некорректная подстановка не является ошнбкой; см разлел 8.3.!. стр.
)29) прн наличии функции, для которой вывод завершается успешно, код оказывается корректным. 11.1. Процесс вывода 195 тнп Р, а тип А получается нз него путем сведения (с)есау!пя) типов массива или функции г к указателю на соответствующий тип. При этом квалификаторы верхнего уровня сопят н чо1аТг1е игнорируются. тепгр1ате<турепаме т> чойс) т(т); // здесь Р— это т тепгр1ате<турепапге т> чогс] д(тй); // здесь Р— это тоже т с)оцЬ1е х[20]; гпт сопвТ вечен = 7; // Параметр // Параметр // Параметр // Параметр // Параметр // Параметр // // й(х) г д (х); ~ (вечен); д(вечен); й(7); д(7); не передается по ссылке: Т вЂ” с)оиЬ1е* передается по ссылке: Т вЂ” с)оиЬ1е[20] не передается по ссылке: Т вЂ” 1пТ передается по ссылке: Т вЂ” 1пТ сопвТ не передается по ссылке: Т вЂ” Тпт передается по ссылке: Т вЂ” гпТ => ОШИБКА: не удастся передать 7 как параметр типа гпсй При вызове функции й (х) тип массива х сводится к типу с]оиЬ1е* как следствие того, что Т вЂ” это тип с]оиЬ1е.
Прн вызове Т (вечеп) квалификатор сопвТ опускается, поэтому делается вывод, что Т вЂ” это тип ТпТ. Если же вызывается функция д (х), то компилятор делает вывод, что Т вЂ” это тип с)оцЬ1е [20] (сведения не происходит). Аналогично, поскольку в вызове д (вечеп) в качестве аргумента используется !ча1пе типа 1пт сопят и поскольку квалнфикаторы сопит и чо1ат11е для передаваемых по ссылке параметров не опускаются, приходим к выводу, что Т вЂ” это тип з.пс сопяТ. Однако обратите внимание, что при вызове д (7) компилятор заключил бы„что Т вЂ” это тнп гпт (поскольку в гча1пе-выражениях, которые не находятся в определении класса, не могут присутствовать квалификаторы типов сопвТ и чо1аТ11е).
В результате этот вызов привел бы к ошибке, поскольку аргумент 7 нельзя передать параметру типа 1пт й. Тот факт, что для аргументов, которые передаются по ссылке, не происходит сведение, может привести к неожиданным результатам в тех случаях, когда эти аргументы являются строковыми литералами. Еще раз рассмотрим шаблон мах ( ) . Тегар1асе<Турепаме Т> Т сопвТй мах(Т сопвТй а, Т сопвсй Ь) г Сведение — зто термин, которым обозначается неявное преобразование типов массива н функции в соотвстсгнуюпгня тнп-указатель. Разумно было бы ожидать, что в выражении мах ( >дрр1е", "Реаг" ) параметр Т будет выведен как тип сЬак сопят*.
Однако строка >дрр1е> принадлежит типу С)гах СОПВТ [б), а СтрОКа >РЕах> — тИПу СЬах СОПВТ [Б]. НИКаКОГО СВЕДЕНИЯ МаССИ- ва к указателю на массив не происходит (поскольку вывод типа вьпюлнястся на основе па- 19б Глава 11. Вывод аргументов шаблонов раметров, передаваемых по ссылке). Таким образом, вместо параметра Т нужно одновременно подставить и тип сьаг сопла [61, и тип сьаг сопев [51, а зто, конечно же, невозможно.
Более подробное обсуждение атой темы можно найти в разделе 5.6, стр. 79. 11.2. Выводимый контекст Типу аргумента могут соответствовать значительно более сложные параметризованные типы, чем просто Т. Ниже приведено несколько (все еще не слишком сложных) примеров. Сетр1аге<еурепаые Т> йс[ Н(Т*); Сетр1аее<гурепате Е, Тпг Ы> чо1 с( Й2 ( Е ( й) [И) ); Сежр1аее<еурепате Т1, куренные Т2, Сурепаве ТЗ> чойс1 ЕЗ (Т1 (Т2:: *) (ТЗ*) ); с1авв Я( риЬ11с: чоЫ 6(доиЬ1е*); ); чоЫ д (1пс*** ррр) ( Ьоо1 Ь[421; 61(ррр); // Выводится, что Т = зпг** й2(Ь); // Выводится, что Е = Ьоо1, а и = 42 ЙЗ(йЯ::й); // Выводится, что Т1 = чойс), Т2 = Я, // а ТЗ = боиЬ1а Сложные обьявления типов составляются из более простых конструкций (операторов обьявлений указателей, ссылок, массивов и функций, объявлений указателей на члены, идентификаторов шаблонов и т.д.).
Процесс определения нужного типа происходит 'в нисходящем порядке, начиная с конструкций высокого уровня и продвигаясь к низкоуровневым. Уместно заметить, что зтим путем можно подобрать тип для большинства таких конструкций; в зтом случае они называются выводимым контекапом (деонсео соп(ех(). Однако некоторые конструкции выводимым контекстом не являются. К их числу относятся следующие: ° полное имя типа; имя типа наподобие О<Т>::х никогда не используется для вывода параметра шаблона Т; ° выражения, не являющиеся типами, которые не являются параметрами, не являющимися типами; например, имя типа я<1+1> никогда не используется для вывода параметра 1, нли параметр Т не выводится путем сравнения с параметром типа йпг(й)[вйкеой(Я<Т>)1. ) ).2.
Выводимый контекст )97 Эти ограничения не вызывают удивления, поскольку в приведенных примерах вывод может оказаться неоднозначным (может даже оказаться, что подходящих типов бесконечно много), хотя случай с полным именем типа иногда легко не заметить. Если в программе встречается невыводимый контекст, это еще не означает, что программа содержит ошибку или что анализируемый параметр не может принимать участия в выводе типа. Чтобы это проиллюстрировать, рассмотрим более сложный пример. // беса11в/карры.срр сегпр1асе <1пс ш> с1авв Х ( ри)э11с: Сурег1ег 1пе 1; чойс) б(1пе) ( ) Сетр1аее<йпе Ы> сои 1рргп(чей (Х<)Ч>:: *р) (Х<)Ч>:: 1) ) Тпс зпа1п() ( Йррш(ах<33>:гб); // Все в порядке; вывод: М = 33 ) Конструкция х<Ш>:: 1, которая находится в шаблоне функции бррт ( ), не является выводимым контекстом; однако использующийся в ней компонент Х<Ы>, указывающий на принадлежность классу и являющийся составной частью указателя на член класса,— зто выводимый контекст.
Когда выведенный из этого компонента параметр )Ч подставляется в невыводимый контекст Х<М>:: 1, получается тип, совместимый с типом фактического аргумента (ах< 3 3 >:: б). Таким образом, для этой пары "аргумент-параметр" вывод удастся успешно выполнить до конца. Обратное утверждение тоже верно, т.е. если параметр типа состоит только из выводимого контекста, то это еще не означает, что вывод не приведет к противоречиям. На~ример, предположим, что у нас имеются надлежащим образом объявленные шаблоны х и у.
Рассмотрим приведенный ниже код. Семр1аее<еурепшпе Т> чоЫ б(Х<У<Т>, 1<Т»); .ои 9() ( б(х<у<1пс>, у<1па» ())г // Все в порядке б(х<х<1пс>, у<с)так» ()); // ОШИБКА: вывод неудачен 198 Глава 11. Вывод аргументов шаблонов Проблема, связанная со вторым вызовом шаблона функции й ( ), заключается в том, что для параметра т на основе двух аргументов функции выводятся разные типы, что приводит к противоречию. В обоих вызовах аргументы функции являются временными объектами, полученными путем вызова конструктора по умолчанию дая шаблона класса Х.
11.3. Особые ситуации вывода Возможны две ситуации, в которых использующаяся для вывода пара (А,Р) не берется из аргументов вызова функции и параметров шаблона функции. Первая — это когда вместо имени шаблона функции используется адрес этого шаблона. В этом случае Р— это параметризованный тип, который находится в операторе обьявления шаблона функции, а А — тип функции, на которую ссылается инициализируемый указатель или указатель, которому присваивается значение. Например: Сегар1асе<гурепаше Т> чоЫ й(Т,Т); чо16 (*р1) (с)таг, с)заг) = йй; Здесь Р— это чоЫ (Т, Т), а А — чоЫ (с)заг, с)таг) . В результате вывода получается, что вместо параметра Т нужно подставить тип с)заг, а указатель рй — инициализировать адресом экземпляра 1<с)гаг>.
Другая особая ситуация связана с шаблоном оператора преобразования типа. с1авв Я ( рц)э11с: сешр1аге<гурепате Т, 1пг Ь)> орегагог Т[Ы]й() ); В этом случае пара (Р,А) получается таким образом, как если бы в нее входил аргумент того типа, к которому мы пытаемся преобразовать параметр типа, возвращаемый оператором преобразования.