Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 29
Текст из файла (страница 29)
Добавим, что если ключевое слово сешр1асе не является необходимым, то его использование запрещено . Нельзя насыщать код квалификаторами б шаблонов "просто так". б На самом деле ю текста стандарта это ие очевидно, но те, кго работал над этой частью стандарта, согласны с данным утверждением. Глава 9. Имена в шаблонах 158 9.3.4. Зависимые имена в объявлениях ибшя Объявления ив1пд могут быть привнесены в имена из двух мест — пространств имен и классов.
Случай пространств имен в данном контексте нас не интересует, поскольку не существует шаблонов пространств имен. Что касается классов, то в действительности обьявления пвхпд привносятся только из базового класса в порожденный. Такие объявления ив1пд в порожденном классе ведут себя как "символические связи" (илн "ярлыки"), направленные из порожденного класса к базовому, обеспечивая таким образом членам порожденного класса доступ к соответствующему имени базового класса, как если бы оно было объявлено в порожденном классе. Краткий пример, не содержащий шаблонов, проиллюстрирует сказанное лучше, чем множество слов. с1авв ВХ ( риЫ1с: уо16 х(1пс); уоЫ й(с)зат сопвк*); И д()> с1авв ВХ : ртхуасе ВХ ( риЬ11с: пвхпд ВХ::Й1 Объявление ив1пд привносит имя й из базового класса ВХ в порожденный класс ЭХ.
В данном случае это имя ассоциировано с двумя разными объявлениями; таким образом ' подчеркивается, что мы имеем дело с механизмом для имен, а не с отдельными объявлениями. Заметим также, что такой вид ияепд-объявления может сделать доступным член класса, который в противном случае был бы недоступен. Базовый класс ВХ (и соответственно его члены) является закрытым по отношению к классу ВХ, за исключением функций ВХ:: й, которые введены в открытом интерфейсе ЭХ и являются, следовательно, доступными для клиентов ВХ. Поскольку механизм ивхпд-объявлений перекрывает использовавшийся ранее механизм объявлений доступа, последний не рекомендован к применению (и в будущих версиях С++ может быть исключен из стандарта). с1аев ВХ : рт1уате ВХ ( ри)з11с~ ВХ::Й; // Синтаксис объявлений доступа.
Не // рекомендован к использованию; взамен // предлагается иехпд ВХ::й Вы уже должны сами представлять проблему, возникающую, когда ивхпдобъявление привносит имя из зависимого класса. Хотя вы и знаете об имени, неизвестно. является ли оно именем типа, шаблона или чем-либо еще. сеюр1атеесурепаве Т> с1авв ВХТ ( риЫ1с: 159 9.3. Синтаксический анализ шаблонов куреней Т Мувсегу; сшар1асе<гурепаше ()> вегас Мад1с; Сешр1асе<сурепаше Т> с1авв )3ХТТ: ргйчаее ВХТ<Т> ( ри)з11с: ивйпд сурепаше ВХТ<Т>::Мувсегу; Мувсегу* р; // Если бы не Гурепаше, зта строка // была бы ошибочна )' Если вы хотите, чтобы зависимое имя было введено с помощью пвйпд-объявления для обозначения типа, то должны явно указать зто путем вставки ключевого слова гурепаше.
Как ни странно, но стандарт С++ не предоставляет аналогичного механизма для того, чтобы пометить такие зависимые имена как шаблоны. Приведенный ниже фрагмент кода иллюстрирует зту проблему. сешр1асе<сурепаше Т> с1авв ВХТМ: ргйчасе ВХТ<Т> ( ри)з11с: ивйпд ВХТ<Т>::Гешр1асе Мадйс; // ОШИБКА: не соответствует стандарту Мадйс<Т>* р11п)с; // СИНТАКСИЧЕСКАЯ ОШИБКА: Мадйс ); // не является известным шаблоном Наиболее вероятно, что зто недосмотр и впоследствии стандарт будет изменен таким образом, чтобы рассмотренная конструкция была корректной. 9.3.5.
АРЬ н явные аргументы шаблонов Рассмотрим приведенный ниже пример. пашеврасе Ш ( с1авв Х ( Гешр1аее<1пс 1> чоЫ ве1есс (Х*); ) той д (М:: Х* хр) ( ве1есг<3>(хр); // ОШИБКА: А))Б не выполняется ) В атом примере логично было бы предположить, что в вызове ве1есг<3> (хр) шаблон ве1есс ( ) отыскивается с помощью А)31.. Однако зто ие так, поскольку компилятор Глава 9. Имена в шабаонах (б0 не может принять решение о том, что кр является аргументом вызова функции, пока не будет решено, что <3> является списком аргументов шаблона И наоборот, невозможно решить, что <3> является списком аргументов шаблона, пока не выяснится, что ве1есс ( ) представляет собой шаблон. Поскольку эту проблему курицы и яйца разрешить невозможно, выражение аналюируется как ( ве1есс < 3 ) > (хр), что не имеет смысла 9.4.
Наследование и шаблоны классов Шаблоны классов могут порождать производные классы или сами быть производными классами. В большинстве случаев особой разницы между сценариями с использованием шаблонов и без них нет; однако есть один важный тонкий момент при порождении шаблона класса из базового класса, обращение к которому выполняется с помощью зависимого имени. Давайте сначала рассмотрим более простой случай независимых базовых классов.
9.4.1. Независимые базовые классы В шаблоне класса независимый базовый класс является классом с завершенным типом, который может быть определен без знания аргументов шаблона. Другими словами, для обозначения этого класса используется независимое имя. сешр1асе<сурепаше Х> с1азв Вазе ( риЫ1с: Тпс Ьавейсе16; Суре6еЕ Тпс Т; с1авв О1: риЫ1с Вазе<вазе<чо16» ( // В действительности зто не шаблон райс: чо16 х() ( Ьавейзе16 = 3; // Обычный доступ к ) // унаследованному члену класса Сешр1аее<сурепаше Т> с1авв В2: риЫТс Вазе<6оиЫе> ( // Независимый базовый класс риЫ1с: чо16 й() ( Ьавехсе16 = 7; // Обычный доступ к ) // унаследованному члену класса Т вегапде // Т здесь — Ваве<6оиЬ1е>::Т, // а не параметр шаблона! 9.4.
Наследование и шаблоны классов 161 ( с)2.векапде = р; // ОШИБКА: несоответствие типов! Такое поведение далеко не интуитивно и требует от разработчика порожденного шаблона внимания по отношению к именам в независимых базовых классах, от которых он порожда- ется, даже когда это порождение является непрямым или имена являются закрьпыми. 9.4.2. Зависимые базовые классы В предыдущем примере базовый класс был полностью определенным и не зависел от параметра шаблона. Это означает, что компилятор С++ может искать независимые имена в тех базовых классах, где видимо определение шаблона.
Альтернатива (не разрешенная стандартом С++) заключается в отсрочке поиска таких имен, пока шаблон не будет инстанцирован. Недостаток этого подхода состоит в том, что до инстанцирования откладывается все сообщения об ошибках. Поэтому в стандарте С++ указано, что поиск независимого имени, присутствующего в шаблоне, происходит немедленно после того, как компилятор столкнется с ним. Рассмотрим с учетом сказанного приведенный ниже пример. тешр1апе<курепаше Т> с1авв О)): рцЬ11с Ваае<Т> ( рцЬ11 с: чоЫ х () ' Ьавейае16 > О; // зависимый базовый класс // (1) проблема.. сешр1асес> // с1ава Ваве<Ьоо1> ( рнЬ11с: епшп ( Ьавесае1б = 42);// Явная специализация (2) Небольшой трюк гоаб д(т))Э<Ьоо1>Н 6) ( б.с (); // (3) 7 Поведение независимых базовых классов в шаблонах очень похоже на поведение базовых классов в обычных нешаблонных классах, однако здесь имеет место некоторая досадная неожиданность: когда поиск неполного имени выполняется в производном шаблоне, независимые базовые классы рассматриваются до списка параметров шаблона Это означает, что в предыдущем примере член класса впкапде шаблона класса )Э2 всегда имеет тип Т, соответствующий Ване<с)опЬ1е>::Т (другими словамн, Тпс).
Например, следующая функция с точки зрения С++ некорректна (при использовании предыдущих объявлений): чоЫ д ())2<1пс*>Ь 62, Тле* р) ( Глава 9. Имена в шаблонах 162 В точке (1) имеется осыпка на независимое имя Ьавей1е1с(, поиск которого следует провести немедленно. Предположим, что оно найдено в шаблоне Вазе и связано с членом класса с типом 1пс в этом классе. Однако после этого компилятор встречает явную специализацию данного класса. Когда это происходит, смысл члена класса Ьавеййе1с( изменяется — при том, что его старый смысл уже использован! Так, при инстанцировании определения РР:: б в точке (3) выясняется, что независимое имя в точке (1) связано с членом класса типа Тпрр преждевременно — в РР<Ьоо1> не существует переменной Ьавей1е1с(, которой можно было бы присвоить новое значение (теперь это элемент перечисления из специализации в точке (2)), так что компилятором будет выдано сообщение об ошибке.
Чтобы обойти эту проблему, стандарт С++ гласит, что поиск независимых имен ие проводится в зависимых базовых классах (однако сам поиск выполняется, как только 7 эти имена встречаются компилятором). Таким образом, соответствующий стандарту С++ компилятор выдаст диагностику в точке (1). Для исправления кода достаточно сделать имя Ьавей1е16 зависимым, поскольку поиск зависимых имен может проводиться только во время иистанцирования шаблона, а к этому моменту точная специализация базового класса, где будет вестись поиск, уже будет известна. Например, в точке (3) компилятор уже будет знать, что базовым по отношению к ВВ<Ьоо1> является класс Ваве<Ьоо1>, явно специализированный программистом.
Сделать имя зависимым можно, например, как показано ниже. // Вариант 1: Еешр1аее<сурепаше Т> с1авн РВ1: рцЬ11с Ване<Т> ( риЬ11с: чойс( б()( сЬ1в->Ьавей1е16 = О; // Поиск отложен Еще один вариант — введение зависимости с помощью полного имени. // Вариант 2: сЕвр1асЕ<курЕПаШЕ Т> с1авв ВР2: риЬ11с Ваве<Т> ( риЬ11с: чойс( й() ( Ване<Т>::ЬавеййеЫ = 0; ) Применение этого варианта требует особой тщательности, поскольку если неполное независимое имя используется для формирования вызова виртуальной функции, то уточнение подавляет механизм виртуального вызова и смысл программы изменяется.
Несмотря на это, существуют ситуации, когда первый вариант нельзя использовать и приходится применять альтернативный. Это часть тах называемого правила двукфазиага иваска, и котором различаются первая фаза, когда определения шаблона встречаются впервые, н вторая фаза, когда происходит инсталлирование шаблона (см. раздел 10.3.1, стр. 170). 9.4. Наследование и шаблоны классов 163 семр1аге<сурепшае Т> с1авв В ( рцЫТс: епша Е (е1 = б, е2 = 28, е3 = 496 ); чйгтпа1 чойс( лего(Е е = е1); чйггпа1 чойс) опе(Ей); сетр1аге<гурепате Т> с1авв )З: рцЬ11с В<Т> ( рцЫТс: чойс) й(] ( сурепке 0<Т>::Е е; // СМв->Е синтаксически некорректно сМв->кето(); // П<Т>::гего(] подавляет виртуальность опе(е)г // опе является зависимым именем в силу // зависимости аргумента функции Заметим, что имя опе в вызове опе (е] зависимо от параметра шаблона просто потому, что тип одного из явно заданных аргументов вылова является зависимым.
Неявно используемые аргументы по умолчанию с типом, который зависит от параметра шаблона, во внимание не принимаются, поскольку компилятор не может их проверить до тех пор, пока не будет проведен поиск, — все та же проблема курицы и яйца. Чтобы избежать таких нюансов, предпочтительно использовать префикс е1п.в-> во всех ситуациях, где это только можно, — даже для нешаблонного кода. Если вы обнаружите, что повторяющиеся квалификаторы загромождают ваш код, можно внести имя из зависимого базового класса в порожденный класс раз и навсегда // Вариант 3: ьетр1аге<сурепате Т> с1авв )З)ЗЗ: рцЬ11с Ваве<Т> ( райс: цвйпд Ваве<Т>::ЬавеНе16; // (1) Теперь зависимое // имя в области видимости чо16 й() ( Ьавеййе1д = Ог ) // (2) Все в порядке Поиск в точке (2) успешен и находит объявление цвйпд в точке (1).