Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 28
Текст из файла (страница 28)
153 9.3. Синтаксический анализ шаблонов Если Х является именем шаблона класса, то в предыдущем выражении целое 0 приводится к типу Х<1>, сгенерированному из этого шаблона Если Х не является шаблоном, то предыдущее выражение эквивалентно следующему: (Х<1) >О Другими словами, Х сравнивается с 1, а результат этого сравнения — "истина" или "ложь" (которые в данном случае неявно преобразуются в 1 или 0 ) — сравнивается с О. Хотя код, подобный приведенному, используется редко, он является корректным кодом С++ (и, кстати, корректным кодом С). Следовательно, синтаксический анализатор С++ будет проводить поиск имен, находящихся перед <, н интерпретировать < как угловую скобку, только если имя является именем шаблона; в противном случае < служит обычным символом оператора "меньше чем".
Такая форма контекстной чувствительности — одно из неудачных последствий выбора угловых скобок для ограничения списка аргументов шаблона. Ниже приведен пример еше одного такого следствия. Сетр1аке<Ьоо1 В> с1авн 1пчегс ( риЬ11с: есагйс Ьоо1 сопев гени1к = !В; чоЫ д() ( Ьоо1 Кеес = В<(1>0)>:: геви1К; // Требуются скобки! ) Если опустить скобки в выражении В< (1>0 ) >, то символ "больше чем" будет ошибочно принят в качестве закрывающего список аргументов шаблона. Это сделало бы код не- 4 верным, поскольку компилятор воспринял его как эквивалент ( (В<1>) ) 0>:: гени1С . Лексический анализатор также не лишен проблем, связанных с угловыми скобками.
Ранее (см. раздел 3.3, стр. 49) уже отмечалась необходимость вставки пробела в случае вложенных идентификаторов шаблона наподобие 11зс<Ььнс<1пк» а; // " --пробел обязателен! Нробел межлу двумя закрывающими угловыми скобками обязателен: без этого промежутка два символа > образуют лексему сдвига вправо» и, следовательно, никогда не будут интерпретироваться как две отдельные лексемы. Это следствие так называемого принципа поиска лексемы максимальной длины. Он заключается в том, что компилятор должен собирать лексему насюлько много последовательных символов, насколько зто возможно.
а Отметим, что двойные скобки, которые используются двя того, чтобы избежать синтаксического анализа выРажения (н<1>) О квк оператора приведения, — еще один источник синтаксической неопределенности. 154 Глава 9. Имена в шаблонах Именно этот вопрос наиболее часто становится камнем препсновения для начинаюших пользователей шаблонов. Ряд компиляторов С++ модифицированы таким образом, что распознают эту ситуацию и интерпретируют» в такой ситуации как два отдельных символа > (выводя предупреждение о том, что это некорректный код С++). Комитет по стандартизации С++ обсуждает также вопрос о том, чтобы при пересмотре стандарта сделать это поведение обязательным (см раздел 13.1, стр. 231).
Еще один пример неприятностей, связанных с принципом поиска лексемы максимальной длины: с угловыми скобками следует аккуратно использовать оператор разрешения контекста (:: ). с1авв Х ( ); ьйвс<::х> шалу Х; // СИНТАКСИЧЕСКАЯ ОШИБКА! Здесь проблема заключается в том, что последовательность символов <: является так называемым диграфом, т.е. альтернативным представлением символа (. Следовательно, 5 компилятор фактически получает выражение, эквивалентное Ьйвв (: Х> папу Х;, которое лишено всякого смысла Здесь также решением проблемы будет добавление пробельного символа.
Ывс< ::Х> пшпу Х; // "--пробел обязателен) 9.3.2. Зависимые имена типов Проблемы с именами в шаблонах не всегда удается удовлетворительно классифицировать. В частности, один шаблон не может заглянуть в другой шаблон, поскольку содержимое последнего может оказаться некорректным в силу явной специализации (более подробно данный вопрос освещен в главе 12, "Специализация и перегрузка"). Ниже приведен несколько искусственный пример, иллюстрируюШнй данное утверждение. Сетр1аге<еурепаше Т> с1авв Тгар ( риЬ11с: епшп ( х ); // (1) х не является типом севр1асе<сурепаше Т> с1авв Чески ( рпЫ1с: Тпс у; чоЫ росй() 5 д фыб б ° в, быуо вводиск о " С иаР иык тилак клавиатур, в частности иа тек, гле отсутствуют некоторые символы (такие, квк а, 1 и 1).
9.3. Синтаксический анализ шаблонов 155 Тгар<Т>:кх>у; // (2) Объявление или умножение? сешр1асе<> с1авв Ткар<чойй> ( риЬ11ск Сурейей Тпк х; // Специализация! // (3) Здесь х является типом ноЫ Ьоош(Тгар<чоЫ>а ЬошЬ) ( ЬошЬ.роой (); Когда компилятор выполняет синтаксический анализ строки (2), он должен решить, с какой конструкцией он имеет дело — с объявлением или умножением. Это решение, в свою очередь, основывается на том, является ли зависимое полное имя Ткар<Т>::х именем типа. Неплохо бы, конечно, заглянуть в этот момент в шаблон Тхар; тогда бы вы увидели, что, согласно строке (1), Тгар<Т>::х не является типом, поэтому для строки (2) остается только умножение. Однако несколько позже это заключение оказывается ложным, поскольку для случая, когда Т является чо16, имеется специализация шаблона, в которой Ткар<Т> к: х представляет собой тип Тпк.
В определении языка зту проблему можно решить следующим образом: указать, что в общем случае зависимое нмя ие является типом, за исключением тех ситуаций, когда это лмя предваряется ключевым словом сурепаше. Если же оказывается, что после подстановки аргументов шаблона это имя не является именем типа, значит, программа ошибочна н компилятор С++ должен сообщить об этом в момент инстаицирования шаблона.
Заметим, что такое применение Сурелаше отличается от использования этого ключевого слова лля обозначения параметров шаблона, являющихся типом. В отличие от указания параметров типа, заменить сурепаше ключевым словом с1авв в описанной ситуации нельзя. Предварять имя ключевым словом сурепаше необходимо в следующих случаях: ° когда это имя находится в шаблоне; ° если оно является полностью квалифицированным; ° если оно не используется в качестве списка спецификаций базового класса или в списке инициализаторов членов в определении конструктора; ° если оно является зависимым от параметра шаблона Кроме того, предварение ключевым словом сурепаше яе разрешается, если справедливы по крайней мере первые три условия.
Чтобы проиллихтрировать это, рассмотрим следуголшй (содержащий ошибки) пример: Сешр1аке<курепаше, Т> 8СКЦСС Я: СУРЕПаШЕ> Х<Т>::Паве ( Глава 9. Имена в шаблонах 156 Я(): Сурепашез Х<Т>::Вазе(пурепазпе, Х<Т>::Вазе(0)) () Сурепагве, Х<Т> й() Сурепате, Х<т>::С * р; // Объявление указателя Р Х<т>::В * с(' // Умножение! СУРепшает Х<тпк>::С * в; )з всгпсс () Сурепатеа Х<тпк>::С * рс; ): Каждое куренное — корректное либо нет — для удобства указания помечено подстрочным номером. Первое сурепшае, означает параметр шаблона.
К этому первому использованию сурепшае приведенные выше правила не относятся. Второе и третье включение сурепате не разрешается согласно третьему правилу. Именам базовых классов в этих двух контекстах не можег предшествовать Сурепагае. Однако сурепагае4 должно быть применено. Здесь имя базового класса не используется для обозначения того, что должно инициализироваться или порождаться из чего-либо, а является частью выражения для создания временного обьекта Х<Т>::Вазе из аргумента О (если угодно— это можно рассматривать как разновидность преобразования типов).
Пятое сурепазае запрещено, поскольку имя, которое за ним следует (Х<Т>), не является квалифицированным именем. Шестое вхождение требуется, если это выражение предназначено для объявления указателя. В следующей строке ключевое слово сурепате отсутствует, поэтому она интерпрешруется компилатором как умножение. Седьмое сурепате необязательно, поскольку оно удовлетворяет первым трем правилам, и не удовлетворяет четвертому. И наконец, сурепазаез запрещено, поскольку оно не используется внутри шаблона.
9.3.3. Зависимые имена шаблонов Проблема, во многом подобная той, с которой мы столкнулись в предыдущем разлеле, возникает и в случае, когда имя шаблона является зависимым. В общем случае от компилятора С++ требуется', чтобы он интерпретировал знак <, следующий за именем шаблона, как начало списка аргументов шаблона; в противном случае это оператор "меньше чем". Как и в случае с именами типов, компилятор должен предполагать, что зависимое имя не ссылается на шаблон, если программист не обеспечивает дополнительную информацию с помощью ключевого слова сетр1асе. севр1аге<сурепатпе Т> с1авв ЯЬе11 риЬ11с: сетар1асе<1пс и> с1авв 1п ( риЬ11с: севр1асе<1пс М> 9.3. Синтаксический анализ шаблонов 157 с1азз плеер ( рп)>11с: чйтсиа1 чойс) й(); ) Сешр1асе<сурепаше Т, Тпп И> с1азв Мейгб) ( рпЫ1с: чо16 сазе1(Я)зе11<Т>::сешр1асе 1п<в)>::сешр1асе 1)еер<Ы>*р)( р->сешр1апе Пеер<Ы>::Й(); // Запрет виртуального вызова чо1б) сазе2(я)эе11<Т>:гзешр1аее 1п<Т>:гзешр1аее Реер<Т>ар)( р.еешр1аее плеер<)Ч>б:й(]; // Запрет виртуального вызова ) )б В этом несколько запутанном примере показаны ситуации, когда для операторов уточнения имени (::, -> и .) может потребоваться использовать ключевое слово ьешр1асе.
В частности, оно требуется всякий раз, когда тип имени или выражения, предшествующего оператору уточнения, зависит от параметра шаблона, а имя, которое следует за оператором, является идентификатором шаблона (другими словами, имя шаблона, за которым следуют аргументы шаблона в угловых скобках). Например, в выражении р.сешр1аее Реер<Ы>:бй() тнп р зависит от параметра шаблона Т. Следовательно, компилятор С++ не может проводить поиск ()еер для выяснения, является ли это имя шаблоном, и необходимо явно указать это с помощью предшествующего имени ключевого слова сешр1асе: Без этого предваряющего ключевого слова синтаксический анализ р. Реер<И>:: Т ( ) проводится следующим образом: ( (р.)эеер) <ы) >й () .
Заметим также, что может потребоваться использовать ключевое слово Сешр1азе несколько раз в пределах одного полного имени, поскольку сами по себе квалификаторы могут быть уточнены посредством зависимого квалификатора (объявления параметров сазе1 и сазе2 в предыдущем примере). Если опустить ключевое слово сешр1асе в таких ситуациях, то открывающая н закрываюшая угловые скобки анализируются как операторы "меньше чем" и "больше чем".