Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 212
Текст из файла (страница 212)
Технические подробности !етр(аге с!аяк Т> с!акк Ма!г!х ( (гес!ог <Т> о (4(; риЫ!с /пепд Ъесяог<Т> орега!ог*<> (сопя!Ма!г!хй, сопя! Ъес!от Т>Ц; Угловые скобки <> после имени функции-друга указывают на то, что речь идет о шаблонной функции. Ьез этих скобок будет подразумеваться нешаблонная функция. Определснный ниже оператор умно>кения имеет прямой доступ к данным классов Ъес!ог и Ма!г!х: !етр1а!е<с1акк Т> Ъес!ог<Т> орега!ог* (сопя!Ма!г!х<7>с т, сопя! (гес1ог«Т>Ъ о( ( // ... использует т в/г/ и в и/1/ д т прялюго доступа к злелгентвм Друзья не влияют на область видимости, в которой определен шаблонный класс; также они не влияют на область видимости, где этот шаблон используется.
Вместо этого функции-друзья и операторы-друзья находятся путем поиска, основанного на типе аргумента (9 11.2.4, 9 11.5.1). Как и функции-члены, функции-друзья инстанцируются (9 В.13.9.1) только прн их вызовс. В.13.3 Шаблоны как параметры шаблона Иногда в качестве аргументов шаблона полезно передавать шаблоны — а нс классы или объекты. Например: !етр!а1е<с!акк Т, !етр1а1е<с!акк> с!акк С>с/аккХгеу!( С<Т> тени; С<7 > ге/к; Хгегд<Еп!гу, вес!ог> х1; //хранит перекрестные ссылки для записей /Епву) в векторе Хге/дЖесогд, яе!> х2; //хрипит перекресплные ссылииг для записей (йесогй) вожножестве ке! Чтобы объявить шаблон как параметр шаблона, мы должны определить требующиеся ему аргументы.
Например, мы определяем, что С, яв.ляющийся параметром шаблона Хге/;— это класс-шаблон с одним аргументом-типом. Если мы не сделаем этого, мы не сможем использовать специализации параметра С. Точка использования шаблона в качестве параметра шаблона обычно является тем местом, где мы хотилл выполнить его инсганпирование различными типами аргументов (такими как Т и T в предыдущем примере). То сеть мы хотим выразить объявления членов шаблона в терминах другого и|аблона, но нам нужно, чтобы другой шаблон был параметром, который можст задаваться пользователем.
В обычном случае, когда шаблону нужен контейнер, чтобы хранить элементы типа его аргумента, лучше передавать тип контейнера (9 13.6, З 173.1), Аргумен гамп шаблонов могут быть только шаблоны классов. В.13.4. Выведение аргументов шаблона функции Компилятор может сам вывести (логическн определить) тнп аргумента шаблона, Т или ТТ, и аргумент шаблона, не являющийся типом, /, из аргумента шаблона функции с типом, составленным из слсдующих конструкций: 933 В.13.
Шаблоны Здесь агуя Т! — это список параметров, по которому можно определить Т или 1 при помощи рекурсивного применения этих правил, а агуя — это список параметров, ко- торые не допускают выведения. Если не все параметры можно вывести таким обра- зом. вызов неоднозначен. Например: еетр!аге<с!авя Т, с!аяе !У> ио!с!Х(сопя! Т', СУ (') (Щ !лгу (!п!); во!с!6 (сапе! сбаг' р) ( Пр, у); )) т — это сбег, и — !и! 1(р, Ь); 1/ ошибка: невозможна вывести !? ) Глядя на аргумент первого вызова !(), мы легко выводим аргументы шаблона. Глядя на второй вызов Т(), мы видим, что й нс соответствует образцу У(") (1?), поскольку типы аргумента функции й () и возвращаемого ей значения различаются.
Если параметр шаблона можно вывести из более чем одного аргумента функции, результатом каждого выведения должен быть один и тот же тип. Иначе вызов будет ошибочным. Например: сетр!аее с!аяя Т оо!<Ц(Т6Т" р); пои!у (сп! 4 !'(г', Я); 1(ь, Помину)' )(правильно О о!иибка, деус>!ысленнос!пь: Т вЂ” эпго !и! ит! сопИ сбаг? В.13.5. 1урепапте и шаблоны Чтобы сделать обобщенное программирование более простым и общим, контейне- ры стандартной библиотеки предоставляют набор стандартных функций и типов (9 16.3.1).
Например: гетр!а!е<с!аее Т с!аея иесеог( рибуа суре!!е! Т' уегагог; Нега !юг Ьеусп (); !!ега!ог епс! $ Т Т' !уреЯ ТТ<Т Т !уре: Т(') (агдя) !уре (!урез*) (агуя ТТ) Т (!урез*) (агуя Т1) сопя! Т Тй с1аяя !елер!а!е палее<Т Т<1> ТТ::* !уре (Тс*) (агуя) Т(Т::*) (агуя Т1) !уре (") (агуя ТТ) оо1а!!1е Т Т(соля!ап! ехргеяя!оп) с1аяя !етр1а!е пате<1> Т<> !уре Т.
Т (!уре::*) (агуя) !уре(Тс ) (агуя Т1) Приложение В. Технические подробности 934 1етр!а1е<с!азя Т> с!аяя !1я1 ( с!аяя !!а !л ( /* ... "/); риЫ1а 1урес!е/!!аа' 11ега1ог; 11ега1ог Ьеуга (); Цега1ог елал (); Это ллскуплаег нас написать: 1етр!аге<с1аяя С> ооЫУ(СЪ о) ( Сс11ега1ог ! = о.0еугл (); //- // синтаксическая о|наука К сожалению, от коллллилятора нельзя требовать здравого смысла, и он не понимает, что С:й1ега1ог — это имя типа. В некоторых случаях «умный > компилятор мог бы догадаться: понимается лн имя как наименование типа, или же чего-нибудь, что типом не является (подобно функции или шаблону).
Но в общем случае это невозможно. Рассмотрим пример, в котором нет явных подсказок о его смысле: ш1у; 1етр!а1е<с!аяя Т оо!с(у(Т« в) ( Тех (у) //вызов функции или объявление аереленной? 1етр!а1е<с!аяз С> оо!с! Ь (Са о) ( 1уреаате Сспега1ог! = цуеуш (); //... Чтобы заявить о том, что именованная сущность — зто тпп, перед квалифицированным именем можно поставить ключевое слово 1уреаате. В атом отношении 1уреаате напоминает я1гцс1 и с(аяя. Кллочевос слово 1уреаате необходимо, если имя типа зависит от параметров шаблона.
Например: 1етр!а1е<с1азя Т> сои( !е (еес1о г< Т> Ь в) Тсх — зто функция, вызываемая с нслокальной переменной у в качестве аргумента? Или мы объявляем переменную у типа Тсх и, извращаясь, поставили необязательные ' скобки? Можно представить программу, в которой Хсх (д) окажется вызовом функции, а У:ж (у) — объявлением. Разрешение производится просто: если не указано обратного, идентификатор считается относящимся к чему-л о такому, что не является типом или шаблоном. Если мы хотим сказать, что с чем-то нужно обращаться каке типом, мы можем воспользоваться кллочевым словом 1уреаате: 935 В,13. Шаблоны иес!ог<Т>н вега!ог 1=иЬеу!и (); //синтаксическая оишбка: отсутствует 1урепоте гурепате иессог<Т ь Нега1ог ?=ибеуш (); //правьиьно В этом случае компилятору возможно удастся определить, что ?!ега!ог является именем типа в каждом экземпляре типа иес!ог, но от компилятора этого не требуется.
Такое действие является нестандартным и непереносимым расширением языка. Единственный контекст, в котором компилятор может предположить, что имя, зависящее от аргумента шаблона, является именем типа,— это те несколько случаев, когда только имена типов разрешены грамматикой. Например, ато имеет место в специфихаторе-базы 9 Л,8.1).
Ключевое слово !урепате можно также использовать как альтернативу слову с?аяя в объявлении шаблона. Например: 1етр!а!е<!урепате Т ио!«1/(Т) Лично я неважно печатаю, и мне вечно не хватает места на экране, поэтому я предпо- читаю более короткое: 1гтр?а!с<с!азз Т> иоЫ/(?у В.13.6. 1е?т?р(а1е как квалификатор Необходимость в квалификаторе !урепате возникает по? ому, что мы можем обращаться и к членам, которые являются типами, и к членам, которые таковыми не являются.
Аналогично может возникнуть необходимость различать имя члена шаблона от других имен членов. Рассмотрим возможный интерфейс к универсальному диспетчеру памяти: с!аззМетогу( Онекоторыйраспределнпельпал~яти (А!!оса!ос) раЫ!а !етр?а!е<с!азз Т> Т' уе! пеш (); !етр!а!е<с!азз Т> ио!а' ге!вазе (Т&). //- ) 1ет р!а !е<с!аяз А!!оса!ог> иоЩ(А! !оса!ог& т) ~п1 р1 = т де! пят<!п!> (); // синтаксическая ошибка: // 1п1 после оператора «льеньше» шг* р2.= т.!етр!а!еде! пего<!и!> (); //явная квалификация //- т.ге!еояе (р!), // оргуленьп шаблона вывода«пся, , //явная квалификация не нужна т ге!сазе (р2); Явная квалификация де! пеш () необходима потому, сто ее параметр шаблона не может быть выведен.
В таком случае нужно использовать префикс !етр!а!е, чтобы сообщить компилятору (и читающему программу человеку), что де! пеш — это шаблон Приложение В. Технические подробности члена, так что возможна явная квалификация желаемым типом элемента. Без квалификации с ключевым словом ~втр1аге мы получим синтаксическую ошибку, поскольку символ < будет воспринят как оператор «меньше». Необходимость в квалификации с ключевым словом 1етр1агв возникает редко, потому что большинство параметров шаблона выводится. В.13.7. Инстанцирование Имея определение шаблона и его использование, генерация правильного кода становится задачей реализации языка.
Из шаблона-класса и набора аргументов шаблона компилятору нужно сгенерировать определение класса и определения его используемых функций-членов. Из шаблонов функций нужно сгенерировать функции. Этот процесс обычно называют инстанцированивм шаблона. Сгенерированные классы и функции называются специализациями. Когда нужно различать сгенерированные специализации и специализации, написанные программ истом явно (ф 13 5), их называют соответственно сгенерированными специализациямииями и явными специализаииями. Явные специализации иногда также называют специализациями, определяемыми пользователем, или просто пользовательскими. Чтобы использовать шаблоны в нетривиальных программах, программист должен понимать, как имена, используемые в определении шаблона, связываются с объявлениями, н как можно организовать исходный код Ц 13.7).
Но умолчанию компилятор генерирует классы и функции из используемых шаблонов по правилам связывания имен Я В.13.8). То есть программист не обязан явно указывать, какие версии каких шаблонов нужно сгенерировать. Это важно, потому что программисту нелегко точно понять, какие версии каких шаблонов нужны. Часто в реализациях библиотек используются шаблоны, о которых он даже не слышал, а иногда знакомые программисту шаблоны используются с аргументами шаблона неизвестного типа.
Как правило, набор функций, которые нужно сгенерировать, можно узнать только рекурсивной проверкой шаблонов, используемых в прикладных программах. Для такого анализа лучше приспособлены компьютеры, а нс люди. Однако иногда программисту важно иметь возможность точно указать, где пз шаблона должен генерироваться код(~ В.13.10) Сделав это, программист получает полный контроль над контекстом инстанцирования. В большинстве окружений компиляции это также подразумевает контроль над тем, когда именно производится инстанцирование.