Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 214
Текст из файла (страница 214)
(3.8). С.13.6.3. Связывание в точке конкретизации Каждое применение шаблона для фиксированного набора его аргументов определяет точку конкретизации (ро(пг о)' (пзгапг(аг(оп). Эта точка находится в ближайшей охватывающей ее глобальной области видимости или области видимости пространства имен, непосредственно перед обьявлением, использующим шаблон. Например: зоисг Х ( Х(гпг); /* ... */ ); гоЫ а (Х); гетр(иге<с)азз Т> гоЫ((Та) (я(а): ) юЫЬ() ( скгегп гоЫ я (1пн; ) (2); ) //вызывается~(Х(2)); то есть)<Х' (Х(2)) Здесь точка конкретизации для)'расположена перед Ь (), так что функция я(), вызываемая из)(), есть глобальная я(Х), а не локальная я(ии) . Из определения точки конкретизации вытекает, что параметр шаблона никогда не может быть связан с локальным именем или членом класса. Например: гоЩ'( ) ( зггисг Х ( /*...
*/ ); // локальная структура гвсгог<Х> гг // еггок нельзя использовать локальную структуру /У в качестве параметра шаблона // .. ) гетр!ага<с!азз Т> юЫзогг(гвсгог<Т>ь г) ( хогг(г.век(п (), г.впд() ); //используется зог(о стандартной биб-ки Кроме того, неквалифицированные имена, применяемые в шаблонах, также не могут быть связаны с локальными именами. И наконец, даже если шаблон впервые используется внутри класса, неквалифицированные имена из определения шаблона не могут быть связаны с членами этого класса. Такое игнорирование локальных имен существенно для предотвращения неприятного поведения, характерного для макросов: С 13.
Шаблоны 1001 У (без явного указания зш::) с(аез Солев!лег ( гесзог<злз> ю раЫ!с: иоЫ зогз() ( ::зогз(и) з ) У... ): озог((нес(ог<(л(>&) вызывает зМ::зог((), а не Сота!лет:зог(() Если бы зогг(весгог<2за) вызывал хогг(), используя зИ::юг(О, результат был бы тем же, а код — был бы прозрачнее.
Если точка конкретизации для шаблона, определенного в пространстве имен, находится в другом пространстве имен, то для связывания доступны имена из обоих пространств имен. Как обычно, для выбора между именами из разных пространств имен применяются правила разрешения перегрузки (58.2.9.2). Еше раз отметим, что шаблон, используемый несколько раз с одним и тем же набором аргументов, имеет несколько точек конкретизации.
Если при этом связывание независимых имен различно, программа ошибочна. Однако такую ошибку реализациям выявить нелегко, особенно если разные точки конкретизации находятся в разных единицах трансляции. Лучше всего стараться избегать сложностей со связыванием имен, минимизируя применение нелокальных имен в шаблонах и применяя заголовочные файлы для согласования контекстов их использования.
С.13.8.4. Шаблоны и пространства имен Когда вызывается функция, ее объявление может быть найдено даже вне текущей области видимости при условии, что она объявлена в том же пространстве имен, что и один из ее аргументов (88.2.6). Это очень важно для функций, вызываемых из определений шаблонов, поскольку при помощи этого механизма находятся зависимые функции в момент конкретизации.
Специализации шаблонов могут генерироваться в любой точке конкретизации (8С.! 3.8.3), в любой точке, следуюшей за ней в той же единице трансляции, или в единице трансляции, специально созданной для генерации специализаций. Это отражает три очевидные стратегии, которые могут использоваться в реализациях С++ для генерирования специализаций: 1.
Генерировать специализацию сразу, как только первый раз встречается соответствуюший вызов. 2. В конце единицы трансляции генерировать все специализации, необходимые для этой единицы трансляции. 3. Когда просмотрены все единицы трансляции, генерировать все специализации, необходимые программе. Перечисленные стратегии имеют свои преимушества и недостатки; допускаются также и комбинации этих стратегий.
1002 Приложение С Технические подробности В любом случае, связывание независимых имен производится в точке определения шаблона. Связывание зависимых имен выполняется путем просмотра: 1. Имен в области видимости в точке, где определяется шаблон. 2. Имен из пространства имен аргумента зависимого вызова (глобальные функции рассматриваются в пространстве имен встроенных типов). Например: иазиезрасе )з' ( с1аззА (/*...*/); свагу'(А) ! ) сваг Т(1п!) !етр!а!е<с(азз Т> спаг В ( Т !) ( ге!ига Т(!) / ) сйаг с = В (К::А () ); // приводит к вызову М:.2(У::А) Здесь вызов !'(1) очевидно зависим, так что мы не можем связать |'с 2(Ф::А) или су(йзт) в точке определения.
Чтобы сгенерировать специапизацию для В<Ж:А> (Ж::А), реализация ищет функции с именем /() пространстве имен Тт/и находит Ж:: 2(Ж::А) . Программу следует считать неправильной, если можно получить разные результаты, выбрав разные точки конкретизации или разное содержимое пространств имен в разных контекстах генерации специализации. Например: патезрасе !'з' ( с1азз А ( / * ...* / ) ! сйаг Т(А, зп!); зетр1азе<с!азз Т, с1азв Т2> сйагл(Тз, Т2 12) (гезиги1(т,(2) ! ) сйаг с = В(Ю::А (), 'а'); //еггогс возможны разные варианты разртиения/(() / добавка к пространству имен У (зВ.2.9.3) иатезрасе К ( го!д Т(А, сйаг) Мы могли бы сгенерировать специализацию в точке конкретизации и получить вызов У(!у::А,!пг).
Или могли бы подождать и сгенерировать специализацию в конце единицы трансляции и получить вызов /(Ф::А, с!заг) . Следовательно, вызов В(дг::А(), ' и' ) является ошибкой. Вообще-то, это очень неряшливое программирование — вызывать перегруженную функцию между двумя ее объявлениями. В большой программе программист может и не обнаружить подвоха.
В нашем конкретном случае компилятор вылавливает неоднозначность. Но если такие вещи встречаются в разных единицах трансляции, то обнаружить проблему будет намного сложнее — реализации не обязаны вылавливать подобные ошибки. Большинство проблем с неоднозначным разрешением вызовов функций связано со встроенными типами. Поэтому большинство средств лечения проблемы опирается на более осторожное использование аргументов встроенных типов.
С)3. Шаблоны 1003 !пюю(ии) ю гоЫ(Т(1пю) ю гоЫ„О" (сйаг) ю юетр1аюе<с!агв Т> Тд(Тю) (()г(ю) ю ге!ига ((ю) /) сйаг !'(сйаг); сйагс = я('а') //вьюзьюво ется //(сйог); Очевидно, что подобных хитростей лучше избегать. С.13.9. Когда нужны специализации Специализацию шаблона класса нужно генерировать лишь тогда, когда требуется определение класса.
Например, если используется указатель, то определение класса не нужно. Например: с1ат Х; Х рю Ха; // о!с' определения Х не требуется // еп ог: требуется определение Х Шаблонный класс не конкретизируется до тех пор, пока действительно не потребуется его определение. Например: юетр!асс<с!ат Т> с(аьз й(пй ( Т.(пй* зис; //о!сс нет нужды в определении /,юпlс (пока) /... Т(пй<(пю>* р11 //не требуется конкретизации 1.т1с<ию> Т(пй<1пю> 1пйю //здесь уэсе нужно конкретизировать !лп/с<и!> Точка конкретизации находится там, где впервые понадобилось определение. С13.9.1.
Конкретизация шаблона функции Реализация конкретизирует шаблон функции только при использовании (вызове) функции. В частности, конкретизация шаблона класса не влечет за собой конкретизации всех его членов, и даже членов, полностью определенных в рамках объявления классового шаблона. Это дает программисту определенную гибкость при определении шаблона класса.
Например: гетр!псе<с/азз Т> с!азз ь!зю ( // ... го)ю! зогюО ю )ю с(азз 6!ой (/* нет операций сравнения */ ) ю Встроенные типы не имеют ассоциированного с ними пространства имен. Следовательно, разрешение зависимых имен не выполняет разрешения перегрузки объявлений, видимых до и после определения шаблона. Например: Приложение С. Технические подробности 1004 го!дЕ(Е!зз<61оЬ> ь 1Ь, Е!11<111!пя>ь lз) !з.югз() г /У ...
используем операции над !Ь, но не 1Ь.зог(() ) Здесь конкретизируется функция Емг<зггйга>::зогг(), но не функция Ем!<С!оЬ>:: юг! () . Это и уменьшает объем работы, и избавляет нас от необходимости перепроектировать программу. Если бы функция Е!зг<6!оЬ>::югг() была сгенерирована, нам пришлось бы либо добавить в тип 6!оЬ операции, требуемые функцией Е!зг:: юг!(), переопределить югг() так, чтобы она не была членом ЕМ, или применить какой-либо другой контейнер для объектов типа С!оЬ.
С.13.10. Явная конкретизация Запрос на явную конкретизацию состоит в объявлении специализации после ключевого слова (ешр!а(е (за которым не следует символ <): зетрlа(е с!вяз иесзог<!п1>; /У кчосс 1етр(азе !п1ь зесгог<!т>::орега(ог() (гпз); //функция-член гетр(а1е 1пз сопиегз<!и1, доиЫе> (доиЫе) 1 //функция Объявление шаблона стартует с гетр1аге<, в то время как просто гетр!аге означает начало явного запроса на конкретизацию. Заметьте, что гетр!аге предваряет полное объявление; констатации одного лишь имени не достаточно (етрlаге гесзог<!пз>:: орегагог ( ) гетр!а ге сон гег1< 1 пз, доиЫе> ! // синтаксическая оитбко // синтаксическая оитбка Как и в случае вызовов шаблонных функций, аргументы шаблона, которые выводятся из аргументов функций, можно опустить 513.3.1).
Например: гетр!аге !пз сопчегг<!пз, ИоиЫе> (доиЫе) 1 //о/г (избыточно) (етр(аге иа сопгегг<!пг> (доиЫе) ! // о/с Когда явно конкретизируется шаблон класса, конкретизируются и все его функции-члены, Заметьте, что явную конкретизацию можно применить для проверки ограничений 613.6.2). Например: (етр!иге<с!азз Т> сlазз Сай !Ьо ( зоысопзии!пт(Т1) ()оо(1) 1] // ... )1 Р са!1)) от очесу сопл!гас!ос (етр)а(е с)азз Са(!з /оо<!пг>1 р еггог: )оо((п() не определена (етр1азе зо(д Са!Ь )оо<Яьаре*>:: сопл(га(п(з ($Ьаре*) ! р еггот)оо(бборел) не определена Запросы на конкретизацию могут сильно влиять на время компоновки и эффективность повторных компиляций.
Я знаю примеры, когда помешение большинства конкретизаций шаблонов в одну единицу трансляции сокращало время компиляции с нескольких часов до нескольких минут. 1005 С.14. Советы Иметь два определения ши одной и той же специализации — ошибка. Не имеет значения, являются ли они специализациями, определяемыми пользователем (813.5), неявно сгенерированными 6С.!3.7) или явно запрошенными. Однако компилятор не обязан диагностировать множественные конкретизации в разных единицах трансляции. Это позволяет «умным реализациям» игнорировать избыточные конкретизации и, тем самым, избегать проблем, связанных с применением библиотек, использующих явную конкретизацию (ЗС.13.7). Реализации, однако, не обязаны быть умными. Пользователи менее умных» реализаций должны сами избегать множественных конкретизаций.
Самое плохое, что может случиться в противном случае — это отказ запуска программы. Язык С++ не требует применения явной конкретизации. Явная конкретизация — это дополнительный механизм для «ручной» оптимизации процессов компиляции и компоновки 8С.13.7). С.14. Советы 1. Фокусируйтесь на разработке программного продукта, а не на мелких технических деталях; ЗС.1.