Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 38
Текст из файла (страница 38)
Приведенный ниже код иллюстрирует один из возможных вариантов этой ситуации. чо16 1(1пг (й) [20] ) ~ чо1г) д(Я и) ( 1(в) р ) В этом фрагменте делается попытка преобразовать я к типу 1пг. (й) [20 ] . Поэтому ти" А — это 1пг [20 ], а тип Р— это Т [(Ч] . Процесс вывода выполняется успешно, причем в результате получается, что вместо параметра т нужно подставить тип 1пе, а вместо М вЂ” значение 20. 11.4. Допустимые преобразования аргументов 11.4.
Допустимые преобразования аргументов Обычно в процессе вывода аргументов шаблонов предпринимается попытка найти такую подстановку для параметров шаблона функции, при которой параметризованный тип Р будет 1щентичен типу А. Однако, если это невозможно, для типов Р и А приемлемы отличия, перечисленные ниже. ° Если в объявлении исходного параметра присутствует описатель ссылки, в типе, который подставляется вместо параметра Р, может быть больше квалификаторов сопвс и чо1ас11е, чем в типе А.
° Если тип А является обычным указателем или указателем на член класса, допустимо такое его преобразование к заменяемому им типу Р, при котором к типу А добавляется квалификатор сопвс и/или чо1ас11е. ° Если вывод относится не к шаблону оператора преобразования типов, подставляемый вместо параметра Р тип может быть базовым классом типа А или указателем на базовый класс для того класса, на который указывает тип А.
Например: Севр1аге<аурепаве Т> с1авв В<Т> ( ): Севр1аае<гурепаве Т> с1авв В : В<Т> ( ); Севр1аке<еурепаве Т> згоЫ й(В<Т>*) чоЫ д(р<1опд> 61) ( й(ай); // Вывод завершится успешно, если // вместо Т подставить 1опд Ослабление требований к совпадению типов допускается только в том случае, если не удалось добиться полного соответствия.
Однако вывод будет успешным лишь тогда, когда параметру Р с учетом возможностей, предоставляемых допустимыми преобразованиями типов, соответствует только один тип А. 11.5. Параметры шаблона класса Вывод аргументов шаблонов возможен только для шаблонов функций и функций- членов. В частности, аргументы шаблонов классов не выводятся из аргументов, применяемых при вызове одного из конструкторов этого класса.
севр1асе 'Сурепаве Т> с1авв Я ( Глава 11. Вывод аргументов шаблонов Я х(12); // ОШИБКЛ: параметр Т шаблона класса не выводится // из аргумента конструктора 12 11.б. Аргументы функции по умолчанию Как и в обычных функциях, в шаблонах функций можно задавать аргументы, которые по умолчанию будут подставляться в оператор вылова функции. Еешр1асе<сурепате Т> нойс( Тпйг(Т* 1ос, Т сепией ча1 = Т()) ( *1ос = ча1; ) Как видно из этого примера, аргумент функции, подставляемый в оператор вызова по умолчанию, может зависеть от параметра шаблона.
Такой зависимый аргумент по умолчанию инстанцируется только в том случае, когда никакой другой аргумент явно не указан. Исходя из этого принципа, приведенный ниже пример является корректным. с1авв Я рпЫТс: Я(1пе, Тпе); Я в(0, 0)г 1пс пайп() ( Даже если аргумент по умолчанию не является зависимым от параметра типа шаблона, он не может использоваться для вывода аргументов шаблона.
Это означает, что приведенный ниже фрагмент кода в С++ недопустим. Еешр1асе<еурепате Т> чо14 б (Т х = 42) ( риЫТс: Я(Т Ы : а(Ь) 1 ргйуаке: Т а; )з 1п1е(йв, Я(7,42) ); // // // // Т() для случая Т = Я является некорректным выражением, однако из-за явного указания аргумента Т() не инстанцируется 11.7. Метод Бартона-Нэкмана 201 1пг вайп() ( б<1пс>()З // Все в порядке: Т = Тпг г()з // ОшиБкА: т невозможно вывести из // аргумента по умолчанию 11.7. Метод Бартона-Нэкмана В 1994 году Джон Бартон (1ойп А Вагзоп) и Ли Нэкман (1.ее В. Нас)опав) представили метод применения шаблонов, названный ими ограничелнььи расширением шаблонов (гевзпсзес1 зешр!азе ехрапз(оп).
Частично причиной развития этого метода послужил тот факт, что в то время шаблоны функций нельзя было перегружать, а пространства имен з в большинстве компиляторов были недоступны. Чтобы проиллюстрировать указанный метод, предположим, что у нас есть шаблон класса Аггау, в котором требуется определить оператор равенства ==. Одна из возможностей — объявить этот оператор членом класса. Однако недостаток этого подхода состоит в том, что первый аргумент (связанный с указателем гЬТв) подчиняется правилам преобразования типов, отличным от тех, которые применимы ко второму аргументу. Поскольку удобнее, чтобы оператор == был симметричным относительно своих аргументов, лучше объявить его как функцию в области видимости пространства имен. Общая схема такого подхода к реализации оператора == может иметь следующий вид: севр1асе<сурепаве Т> с1авв Аггау ( риЬ11с: Гевр1аге<гурепаве Т> Ьоо1 орегасог == (Аггау<Т> сопзга а, Аггау<Т> сопвгд Ь) Олнако если перегрузка шаблонов функций не допускается, возникает проблема: в этом пространстве имен других шаблонов операторов = = объявлять нельзя, а ведь они могут понадобиться для других шаблонов классов.
Бартону и Нэкману удалось решить зту проблему путем определения в классе оператора равенства в виде обычной функции-друга. з Возможно, вям стоит прочитать раздел 12.2, стр. 208, чтобы понять, как в современном С++ работает перегрузка шаблонов функций, Глава 11. Вывод аргументов шаблонов Гетпр1асе<сурепагае Т> с1авв Аггау ( рцЬ11с: йгйепс) Ьоо1 орегаеог == (Аггау<Т> сопиев а, Аггау<Т> сопвсй Ъ) ( гесцгп АггаувАгеЕс(ца1 (а, Ь) ' Предположим, что эта версия шаблона Аггау инстанцируется для типа Е1оак.
Тогда в процессе инстанцнрования объявляется функция-друг, с помощью которой реализован оператор равенства. Заметим, что сама по себе эта функция не есть результат инстанцирования шаблона функции. Это обычная функция (а не шаблон), введенная в глобальную область видимости, которая является побочным эффектом процесса инстанцирования. Поскольку это нешаблонная функция, ее можно перегружать с другими объявлениями оператора ==, причем для этого не используется возможность перегрузки шаблонов функций. Бартон и Нэкман дали этому методу название ограниченное расширение шаблонов, поскольку в нем не используется шаблон орегасог== (Т, Т), применимый для всех типов Т (другими словами, неограниченное расширение).
Поскольку оператор орегаког== (Аггау<Т>сопвка, Аггау<Т>сопевши) определен в теле класса, он автоматически является встраиваемой функцией, и поэтому мы решили делегировать его реализацию шаблону функции АггаувАгеЕс(ца1, которая не обязательно должна быть встроенной и которая вряд ли будет конфликтовать с другим шаблоном с таким же именем. В наше время те цели, для которых был придуман метод Бартона-Нэкмана, достижимы и без него, однако это не снижает интерес к данному методу, поскольку он позволяет генерировать в ходе инстанцирования шаблона класса функции, не являющиеся шаблонами. Поскольку эти функции не генерируются нз шаблонов, для них не требуется вывод аргументов шаблонов; к ним применимы обычные правила разрешения перегрузки (см. приложение Б, "Разрешение перегрузки").
Теоретически это может означать, что при проверке соответствия объявленных типов формальных параметров фактическим типам аргументов, с которыми вызывается функция, допускается неявное преобразование этих типов. На самом деле это преимущество незначительно, поскольку в стандартной современной реализации языка С++ (отличающейся от той, которой Бартон и Нэкман пользовались при разработке своей идеи) введенные в глобальную область видимости функции-друзья не видны безоговорочно за пределами своей исходной области видимости. Они видны только после поиска имен, зависящего от аргумента.
Эго означает, что аргументы, с которыми вызывается функция, уже должны быть ассоциированы с классом, другом которого является данная функция. Если же аргументы функции принадлежат типу, не имеющему отношения к классу, другом которого является эта функция, но такому, который можно в него преобразовать, то такая функция-друг найдена компилятором не будет.
) 1.8. Заключение гоз с1азз Я ( Хешр1асе<сурепзхпе Т> с1азз )(гаррег (. рг1масе: т оЬЗесс; риЬ11с: в)гаррег(Т оЬЗ) : оЬЗеос(оЬЗ) ( // Неявное преобразование Т к типу В)гаррег<Т> ) йгхелд чоЫ Е(В)гаррег<Т> сопзгй а) ( 1лс тайп() Я з; В)гаррег<Я> и(з)г б(и); // Правильно: класс В)гаррег<Я> связан с ы Е(з); // ОШИБКА: класс В)гаррег<Я> не связан с з В данном примере вызов функции б (и) корректен, поскольку функция б ( ) является другом класса Хгаррег< Я>, с которым связана переменная и . Однако при вызове б ( з ) обьяв- 4 ление функции-др)та б (хгаррег<я> сопзха ) невидимо, поскольку класс в)гаррег<я>, в котором определена функция б ( ), не ассоциирован с аргументом и типа я.
Поэтому, несмотря на допустимость неявного преобразования типа Я к типу в)гаррег<Я> (с помощью конструктора класса Хгаррег< Я >), такое преобразование не рассматривается. Таким образом, определяя функцию-друг» для шаблона класса, мы получаем незначительное преимущество по сравнению с определением ее в качестве обычного шаблона функции.
11.8. Заключение Вывод аргументов шаблонов для шаблонов функций бып изначально заложен в язык программирования С++. Альтернативный подход с явно задаваемыми аргументами шаблона начал применяться в С++ существенно позже. Многие специалисты по С++ считают возможность введения функции-друга в глобальную область видимости вредной, поскольку при этом программы становятся более чувствительными к порядку инстанцирования.