Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 17
Текст из файла (страница 17)
Будем называть их синтаксическими ограничениями. Синтаксические ограничения— это, например, потребность в наличии конструктора определенного типа, требование, чтобы определенный вызов функции не был неоднозначным, и т.д. Ограничения другого типа будем называть семантическими ограничениями. Зги ограничения поддаются механической проверке намного труднее.
В общем случае делать это даже нецелесообразно. Например, можно требовать, чтобы для параметра типа шаблона был определен оператор < (что является синтаксическим ограничением), но обычно требуется, чтобы этот оператор определял некоторый вид упорядочения в своей области определения (что является семантическим ограничением).
Для обозначения набора ограничений, который постоянно повторяется для библиотеки шаблонов, часто используется термин концепция. Например, стандартная библиотека С++ базируется на таких концепциях, как итератор с произвольным доступом (гапдош асеева йегагог) и конструируемый по умолчанию (де!ко!1 сопя!гас!!Ые).
Концепции могут образовывать иерархии в том смысле, что одна концепция может быть усовершенствованием другой. Более совершенная концепция включает все ограничения базовой концепции с добавлением некоторых новых. Например, концепция итератора с произвольньии доступом является усовершенствованием концепции двунаправленного итератора (Ь!д!гесг!она! нега!ог) в стандартной библиотеке С++. Используя данную терминологию, можно сказать, что отладка кода шаблонов в значительной степени сводится к определению того, как нарушаются концепции в реализации шаблона и при его использовании. 6.6.1. Дешифровка ошибок-романов Текст обычных сообщений об ошибках компиляции, как правило, является лаконичным и отражающим суть ошибки.
Например, когдакомпиляторговорит?е1аин Х !ган по шевЬег ' йпп ' " (в классе Х отсутствует член "йпп"), то обычно не составляет труда определить, что именно неверно в вашем коде (например, вы ошибочно ввели "хоп" вместо "Епп"). В случае шаблонов это не так. Рассмотрим относительно простой фрагмент кода, в котором задействована стандартная библиотека С++. Он содержит небольшую ошибку: используется 11не<всгйпд>, но поиск проводится с помощью объекта- функции дгеасех<йпс>, а не дгеаеех<нсхйпд>: 6.6. Отладка шаблонов вой:: 11вт<втй::всг1Пд> СО11 // Поиск первого элемента, большего "А" зтй:: 11вт<зсй::втгйпд>::Ттегасог роз; ров = всй::ййпй Тй( со11.Ьедйп(),со11.епй(), // Диапазон поиска зтй::Ыпй2пй(втй::дгеатег<1пт>(),"А"));// Критерий поиска Такого рода ошибки часто случаются, когда программист вырезает и вставляет код, но забывает внести в него необходимые изменения.
Одна из версий популярного компилятора ОМ7 С++ выдает прн этом приведенное ниже сообщение об ошибке. /1оса1/Ьпс1ийе/вт1/ а1до.Ь: 1п бипсс1оп 'эстиса ять:: ьаз т Ьтетатот< ять::Ьавйс вттйпд<сЬаг, ять::сЬаг тга1тв<сЬаг>, ЯТЬ::а11осатот<сЬат», ЯТЬ:: Мопсопвт тгаатв< ЯТЬ::Ьав1с зтг1пд<сЬат, ЯТЬ~:сЬаг Сгааез<сЬат>, ЯТЬс:а11осасот<сЬаг» » ять::Нпй 16< ять:: Ывт Ьтегасог< ять::Ьав1с втЫпд<с Ьат, ЯТЬ~:сЬат тта1тв<сЬаг>, ЯТЬ::а11осатог<сЬат», ЯТЬ:: Иопсопвт тта1тв< ЯТЬ::Ьавйс вег1пд<сЬаг, ЯТЬ::сЬаг тгаатв<с Ьаг>, ЯТЬ::а11осатог<сЬаг» », ЯТЬ::Ьйпйег2пй< ЯТЬ::дге атег<1пт» >( ЯТЬ:: Ывт Ттетатот< ЯТЬ::Ьав1с зтппд<сЬаг, ЯТ1::сЬаг Ста1тв<сЬаг>, ЯТЬ::а11осатог<сЬаг», ЯТЬ:: Мопс опвт ттайтв< ЯТ(::Ьавас втгапд<сЬаг, ЯТЬ::сЬаг Сга1тв<сЬаг> ЯТЬ::а11осатот<сЬат» ».
ЯТЬ:: Ывт 1гегатог< ЯТ(.::Ьав Тс вШпд<сЬаг, ятЬ::сЬаг тгайтв<сЬаг>, ятЬ::а11осатог<сЬат », ЯТЬ:: Иопсопвт Сга1тз< ЯТЬ::Ьав1с втг1пд<сЬаг, ЯТЬ::сЬ аг тгаатз<сЬат>, ять::а11осасог<сЬаг» », ятЬ::Ыпйег2пй < ЯТЬ::дгеатег<1пт», ЯТЬ::Ьприт Ттегатог Сад)':/1оса1/1пс 1ийе/вт1/ а1до.Ь!115: Тпвтаптйатей агою ' яТЬ::ййпй Ьй< ЯТ1 Ьйвт Ттетатот< яТЬ::Ьав1с вттйпд<сЬаг, яТЬ::сЬаг тта1тв< сЬаг>, яТЬ::а11осасот<сЬат», яТЬ:: 1)опсопвт ттайтв< ятЬ:: Ьавйс зтг1пд<сЬаг, ЯТЬ::сЬаг тгайтв<сЬаг>, ЯТЬ::а11осасот<с Ьат> » >, ЯТЬ::Ыпйег2пй< ЯТЬ::дгеатег<1пт» >( ЯТЬ:: Ь 1вт Ттегатог< яТЬ::Ьавйс втг1пд<сЬат, яТЬ::сЬаг тга1тв<сЬаг >, ять::а11осатот<сьат», ять::мопсопвс тгайтв< ять::Ьавйс зтт1пд<сЬаг, ЯТЬ::сЬаг тга1тв<сЬаг>, ЯТЬ:: а11осатог<сЬат> » >, яТЬ::Ывт Ттегатог<яТЬ::Ьав1с втт1пд<сЬаг, яТЬ::сЬа г тга1тз<сЬат>, яТЬ:: а11осатот<сЬаг», яТЬ:: Мопсопзт сга Тсз< ять::Ьав1с втгйпд<сЬаг, ЯТЬ::сЬаг тгаатв<сЬат>, ЯТЬ::а 11осатог<сЬаг»» яТь::Ыпйег2пй< яТЬ::дтеатет<1пс») 'т езтргод.срр:18: Тпзтаптйатей бгош Ьеге/1оса1/1пс1ийе/вт1/ а 1до.Ь:78: по шатсЬ бог са11 то '( ЯТЬ::Ьапйег2пй< ЯТЬ::дтеа тет<1пт»! ( ять::ьав1с зттапд<сЬат, ЯТЬ::сЬат тта1тв<сЬаг >, ЯТЬ~:а11осасог<сЬаг» а)'/1оса1/Ьпс1ийе/вт1/ йппсЫоп.Ь :261: сапйайатез аге: Ьоо1 ять::Ыпйет2пй< ятЬ::дгеатег<1п с» ::орегатог ()(сопят Тпт а) сопвт Глава б.
Применение шаблонов на практике Такое сообщение на первый взгляд больше смахивает на роман, чем на диагностическое сообщение, и одним своим видом способно полностью деморализовать новичков в области шаблонов. Однако сообщения, подобные приведенному, при наличии некоторой практики поддаются пониманию, и местонахождение ошибок можно легко определить.
В первой части нашего сообщения говорится, что ошибка произошла в экземпляре шаблона функции (с ужасно длинным именем), запрятанном глубоко внутри заголовочного файла /1оса1/1пс1ис)е/ес1/ а1до.)т. Далее компилятор сообшает, почему он сгенерировал этот конкретный экземпляр шаблона. В данном случае "отсчет" начинается со строки 18 файла гевгргод. срр (это файл, содержащий код нашего примера), в которой вызывается генерация экземпляра шаблона ййпс) 18 из строки 115 заголовочного файла а1до.)з.
Компилятор сообщает все это для того, чтобы вы знали, что такие-то экземпляры шаблонов сгенерированы, и смогли восстановить цепочку событий, которые вызвали генерацию экземпляров шаблона. Однако в случае нашего примера есть основания полагать, что должны быть инстанцированы все шаблоны. Но почему же тогда программа не работает? Ответ на этот вопрос содержится в последней части сообщения, там, где говорится "по васс)з бог са11" — это означает, что вызов функции не может быль сгенерирован из-за несоответствия типов аргументов и параметров. Более того, сразу же за этим в строке, содержащей "санс(1с)асев аг е", поясняется, что единственным типом-кандцдатом является целочисленный тип (тип параметра — сопвс 1пс*).
Вернувшись назад, к строке 18, вы увидите нсс)::Ыпб2пс) (вгс)::дгеагег<1пг> (), "А" ) — строка дейспштельно содержит целочисленный тип (Тпг), а он несовместим с объектами строкового типа, поиск которых проводится в нашем примере. Стоит заменить <Тпг> на вгг):: вггйпд — и проблема будет решена. Нет сомнений в том, что сообщение об ошибке можно было структурировать получше.
Ничто не мешает опусппь описание проблемы перед историей инстанцирования шаблонов, а вместо развернупах имен наподобие "муТевр1аге<уоигТепр1аге<йпс»" выводить структурированные описания, например "(ЧуТевр1асе<Т>", где Т = УоигТевр1асе<йпк>, чтобы сократить чрезмерно длинные имена. Однако нельзя отрицать и то, что вся информация в этом диагностическом сообщении в некоторых ситуациях может оказаться весьма полезной. Поэтому не стоит удивляться тому, что аналогичную информацию выдают и другие компиляторы (хотя в некоторых из них используется упомянутая выше структуризация). 6.6.2. Мелкое инстанцирование Диагностические сообщения, подобные приведенному выше, выдаются, когда ошибка обнаруживается в конце длинной цепочки инстанцирований.
Чтобы проиллюстрировать это, рассмотрим (несколько искусственный) пример кода. Севр1аке <Сурепаве Т> чо1с) с1еаг(Т сопвса р) ( *р = п~ // предполагается, что т 6.6. Отладка шаблонов 101 // является указателем Севр1аее <Гурепаве Т> чойг) соге(Т сопзга р) ( с1еаг(р)р ) севр1аге <гурепаве т> ъойс) вйсЫ1е(турепаве Т::1пдех р) ( соге(р) р ) ,севр1асе <сурепагае т> чо1с) н)зе11(Т сопвга епзг) сурепаве Т:гйпг)ех 1; в1гЫ1 е<Т> ( 1 ); ) с1азз С11епс ри)э11с: Гурес)ей 1пе 1пс)ех; ); С11епг вайп с11епе; Тпг вайп() ( в)зе11(ва1п с11епс) г ) В этом примере иллюстрируется типичное разделение на уровни при разработке программного обеспечения: шаблоны функций высшего уровня, таких как в)ге11 ( ), зависят от компонентов наподобие гайсЫ1е ( ), в которых, в свою очередь, используются такие базовые средства, как соге () .
При инстанцировании з)ге11 () необходимо также инстанцировать все нижестоящие уровни. В данном примере проблема обнаруживается на самом глубоком уровне: соге () инстанцируется с типом йпг (из С11епс:: 1пс)ех в вйгЫ1е ( ) ), и попытка получить значение по указателю для этого типа является ошибкой. Хорошая диагностика включает отслеживание цепочки действий, ведущих к ошибке, через все уровни, но вы уже видели, в каком громоздком виде может выводиться такое количество информации. Превосходное обсуждение вопросов, относящихся к данной проблеме, читатель найдет в [34). В этой книге Бьерн Страуструп (В)агпе З(гона(гпр) определяет два класса подходов, позволяющих заранее определить, удовлетворяют ли аргументы шаблона некотоРому набору ограничений. Это лелается либо с помощью расширения языка, либо за счет предварительного использования параметров.
Первый класс методов частично будет Глава 6. Применение шаблонов на практике 102 рассмотрен в разделе 13.11, стр. 244.Методы второго класса предполагают стимуляцию выдачи всех возможных ошибок путем мелкого инстаицироваиия (з(задом! пз(апбабоп). Это делается следующим образом: в шаблон помещается неиспользуемый код, единственное назначение которого — заставить компилятор сообщить об ошибке, если этот код инстанцируегся с аргументами шаблона, которые не отвечают требованиям более глубоких уровней шаблонов. В нашем предыдущем примере можно добавить в в)те11 () код, с помощью которого можно получить значение по указателю с типом Т:: 1пс(ех.