Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 212
Текст из файла (страница 212)
Причина нашего стремления использовать шаблон в качестве параметра шаблона заключается в том, что мы хотели бы конкретизировать его разными типами аргументов (такими как Т и Т' в предыдущем примере). То есть мы хотели бы выражать объявления членов шаблона в терминах другого шаблона, который был бы параметром и задавался пользователем. Когда шаблону требуется контейнер для хранения элементов с типом аргумента шаблона, лучше передать ему тип контейнера (913.6, 917.3.1). Только классовые шаблоны могут выступать в роли аргументов другого шаблона. С.13.4. Логический вывод аргументов функциональных шаблонов Компилятор может осуществить логический вывод типа аргумента шаблона, Т или ТТ, и нетипового аргумента шаблона — 1 исходя из аргумента шаблона функции с типом, составленным из следующих конструкций: Здесь агдз Т1 — это список параметров, по которому можно определить Тили 1 согласно рекурсивной процедуре применения этих правил, а агдз — это список параметров, не допускающий логического вывода.
Если таким образом можно вывести не все параметры, вызов считается неоднозначным. Например: Т Т* гуре [1) ТТ< Т> Т гуре:: * Т (*) (а«аз] гуре (гуре:: *) (агдз Т1) Т (гуре:: *) (агдз Т1) сопл! Т Ть сгазз гетр1аге пате<Т> Т<!> ТТ::* гуре (Т::*) (а«де) Т (Т:: *) (агдз Т1) гуре (*) (а«де Т1) гога!1!в Т Т [ сопзаапг ехргезз[оп ) с1ат гетргаге пате<1> Т<> гуре Т:: * Т (гурег г*) (агдз) гуре (Т:: *) (агдз Т1) С.13 Шаблоны 993 гетр(а!е<с1ат Т, с1авв 1/> гоЫ Г(соле! Т*, 1/(*) (1/) ) т! о (1пг); юЫ Ь (сопев слог* р) ( Х(р в) ' 1(р Ь) г ) // Тесть сьаг, (/ есть 1т // епззг: невозиопсно вывести (/ !етр1а!е<сйт Т> ю(ь1$(Т1, Т* р); юЫ и ((и! 1) ( Т(1, Ы) // ОЬ Г(1, "КететЬег! ") ) //еггог, неоднозночносты Тесть т(, иви Тесть соле! сБог? ) С.13.5. Шаблоны и ключевое слово ~урелал)е Чтобы сделать обобшенное программирование более простым и более обшим, контейнеры стандартной библиотеки предоставляют набор стандартных функций и типов (916.3.1).
Например: гетр!иге<с(ат Т> сЬгзз нес!ог ( риЬ1!с: Зурег(е/ Т* вега!от Лего!ог Ьея1л ( ) 1!ега!ог еМ() ! // ... )' гетр!а!е<с1аи Т> с1азз 11з! ( с1ат Ила (/* ... */); риЫ!с: <урейе) 1(па* вега!ос; «ега!оь Ьеа!л (); Вега!ог ель!() г // ... )' Это искушает нас написать Глядя на аргументы первого вызова/'(), мы легко выводим все аргументы функционального шаблона. Для второго же вызовами() мы видим, что Ь() не соответствует образцу Е/(*) ( И, поскольку тип аргумента функции Ь () отличается от типа возврашаемого ею значения. Если параметр шаблона выводится из более чем одного аргумента функции, результатом каждого вывода должен быть один и тот же тип.
В противном случае вызов считается ошибочным. Например: Приложение С. Технические подробности 994 <етр!а<е<с!авв С> гоЫ Т(Са г) ( С: <йега<ог <' = ».Ьед(п (); //„. ) // синтаксическая ошибка К сожалению, от компилятора нельзя требовать слишком многого и он не понимает, что С<: Вега<ос — это имя типа.
В некоторых частных случаях «умный» компилятор мог бы распознать, относится имя к типу, или к чему-нибудь еще, что не является типом (к функции или шаблону). Но в общем случае это невозможно. Действительно, рассмотрим пример, лишенный подсказок насчет его смысла: Ы<у< 1етр1а<е<с<азз Т> гоЫд(Та г) ( Т: <х(у) 1 //вызов функянн няи объявление переменной? Является ли здесь Т: <х функцией, вызванной с у в качестве аргумента? Или мы намеревались объявить локальную переменную у с типом Т<:х, поставив здесь по странным причинам необязательные круглые скобки? В принципе можно представить себе реальные программы, в которых Х:: х(у) будет функциональным вызовом, а У::х(у) будет объявлением.
Эта неоднозначность разрешается просто: если не указано обратного, то идентификатор относят к чему-то такому, что не является типом или шаблоном. А если мы хотим трактовать идентификатор как имя типа, мы можем это сделать с помощью ключевого слова <урепате: <етр!а<е<с<авв С> гоЫ Ь (Са г) ( <урепате С<: !<его<ог < = ».Ьеа!п () //... ) Ключевое слово 1урепагпе можно поставить перед квалифицированным именем с целью утверждения, что имя относится к типу. В этом отношении оно напоминает ключевые слова в<гас< и с!авв.
Ключевое слово <урепате необходимо, если имя типа зависит от параметров шаблона. Например: <етр<а<е<с<а»» Т> гоЫ Ь(гес<ог<Т>а г) ( гесыг<7>:: Ьега<ог <' = г. Ьея(п () 1 <урепате гес<ог <Т> <: пега<ос <' = г. Ьед!п (); // ... ) //еггог: отсутствует <урепате // оЬ В этом случае компилятор может быть и смог бы определить, что 1<ега<ог есть имя типа в каждой конкретизации шаблона гес<ог, но стаидартом от него этого не требуется, так как это было бы непереносимым расширением языка. Единственный контекст, в котором компилятор может определить, что имена, зависящие от аргу- С) 3.
Шаблоны мента шаблона, являются именами типов — это несколько частных случаев, в которых лишь имена типов разрешены грамматикой. Например, в случае спецификаторов, перечисленных в 5А.8.1. Ключевое слово 1урепате можно также использовать как альтернативу ключевому слову с!азв в объявлениях шаблонов: 1етр(аге<(уреаате Т> иоЫГ(Т) ! Поскольку я не слишком быстро работаю с клавиатурой и мне всегда не хватает места на экране, я предпочитаю более короткую запись 1етргаге<с!ат Т> гоЫ Г(Т) ! С.13.6. Ключевое слово 1ептр!ате в качестве квалификатора Необходимость в квалификаторе 1урепате возникла потому, что мы можем обращаться к членам классов, которые являются типами, и к членам, которые типами не являются.
Аналогично, имеется необходимость отличать шаблонные члены классов от членов, шаблонами не являющихся. Рассмотрим возможный интерфейс к универсальному менеджеру памяти: с1ат Метису ( риЫ1с: 1етр1аге<с1авл Т> Т" лег пеги () ! гетр(а(е<с1алв Т> гоЫ ге!еаве ( Ть ) //... )1 гетр1аге<с(ат АИосагог> иоЫ Г(АИосагогь т) ( т1* р1 = т.лег птг<!п1> () ! // еггог т1* рЗ = т.
гетр!а1е Евг пеги<(п1> () 1 //явная квалификациЯ //... т. ге1еале (р1) 1 // параметр шаблона выводится (нет нужды в явной квалификации) т . ге1еаве (р2) 1 ) Явная квалификация при вызове яег пе1г() необходима, поскольку здесь логический вывод параметра невозможен. В этом случае нужно использовать префикс гетр!иге, чтобы информировать компилятор о том, что имя Иег пем относится к шаблонному члену класса и явная квалификация желаемым типом элементов возможна, Вез ключевого слова гепруаге будет получено сообщение об ошибке, поскольку знак < будет воспринят как знак операции сравнения «меньше чем».
Это редкий случай, поскольку в большинстве случаев удается выполнить логический вывод параметров шаблона. С.13.7. Конкретизация Генерация корректного машинного кода из определения шаблона и его использования ложится на плечи реализаций языка С+л-. Из определения шаблона класса и набора аргументов шаблона компилятор должен сгенерировать определение класса и определения тех его функций-членов, которые находят применение в программе. 996 Приложение С.
Технические подробности Из определения шаблона функции компилятор должен генерировать конкретные функции. Эти процессы называются конкретизацией шаблона (гетр!а!е гпзгапбабоп)'. Сгенерированные классы и функции называются специализациями (зресга!!соболя).
Когда нужно различить сгенерированные специализации и специализации, написанные программистом вручную (913.5), применяют более подробные термины — сгенерированные специализации (явпегагеб грег!а!!габону) и явные специализации (ехрбсб зрес!а!)сабопв), соответственно. Последний термин имеет и другие вариации — специализации, определяемые пользователем (изег-бе!)пеб зресгайгабопз) или просто специализации.