|
Программирование >> Полиморфизм без виртуальных функций в с++
some type[I] СТ<Т> СТ<1> Т (*)(args) some type (*) (args containing T) some type (*) (args containing I) ТС::* С Т: :* Здесь Т - аргумент-тип шаблона, I - аргумент шаблона, который не является типом, СТ - имя ранее объявленного шаблона класса, args containing T -список аргументов, из которого можно определить Т, применяя эти правила, а С - имя класса. Теперь пример с функцией lookup () становится корректным. Пользователям не надо заучивать этот перечень, так как он просто формализует очевидный синтаксис. Вот другой пример: template<class Т, class U> void f(const T*, U(*)(U)); int g(int); void h(const char* p) { f(p,g); T - это char, и - это int f(p,h); ошибка: невозможно вывести U Глядя на фактические параметры в первом вызове f (}, мы легко можем вывести фактические аргументы шаблона. Смотря на второй вызов f (), видим, что h () не соответствует образцу U (*) (U), поскольку типы аргумента и возвращаемого значения различаются. В прояснении этого и многих других подобных вопросов существенную помощь оказал Джон Спайсер (John Spicer). 15.6.2. Задание аргументов шаблона функции Проектируя шаблоны, я ду.мал о том, чтобы разрешить явное задание аргументов для шаблона функции точно так же, как можно задавать аргументы шаблонов классов. Например: vector<int> v(10); класс, аргумент шаблона int sort<int>(v); функция, аргумент шаблона int Однако от этой идеи пришлось отказаться, потому что в большинстве примеров явно задавать аргументы шаблона не требовалось. Также, я опасался неоднозначностей и трудностей при синтаксическом анализе. Скажем, как следует разбирать этот пример? void g() { f<l>(0); (f) < (1>(0)) или (f<l>) (0) ? Теперь я пе считаю это проблемой. Если f - имя шаблона, то f < - начало квалифицированного имени и последующие лексемы должны интерпретироваться с учетом этого факта; в противном случае < означает меньше . Явное задание может быть полезно, поскольку мы не можем вывести тип возвращаемого значения по вызову шаблона функции: template<class Т, class U> Т convert(U u) { return u; } void g(int i) convert(i); ошибка: нельзя вывести Т convert<double>(i) ; 111 - double, U - int convert<char,double>(i); 111- char, U - double convert<char*,double>(i); 114:- char*, U - double ошибка: нельзя преобразовать double в char* Как и для аргументов функции по умолчанию, в списке явно заданных аргументов шаблона можно опускать только крайние правые элементы. Явное задание аргументов шаблона позволяет определить семейства функций преобразования и создания объектов. Явное преобразование, которое выполняет то, чего можно добиться одним лишь неявным преобразованием, например функция convert () в приведенном примере, достаточно часто требуется и хорошо подходит для включения в библиотеку. Еще один вариант - применить проверку, которая гарантировала бы, что для любого сужающего преобразования можно будет обнаружить ошибку во время выполнения. Мы осознанно сделали похожим синтаксис новых операторов приведения (см. раздел 14.3) и явно квалифицированных вызовов шаблона функции. С помощью новых операторов приведения выражаются действия, которые нельзя описать другими средствами языка. Аналогичные операции, например convert (), можно выразить в виде шаблонов функций, поэтому они необязательно должны быть встроенными операторами. Еще одно применение явно заданных аргументов шаблона функции - управление работой алгоритма за счет задания типа или значения локальной переменной. Например: template<class ТТ, class АТ> void f(AT а) { ТТ temp = а; используем ТТ для управления точностью вычислений ... void g(Array<float>& a) { f<float>(a); f<double>(a); f<Quad>(a); Включение в С++ явного задания аргументов шаблона функции одобрено на заседании комитета в Сан-Хосе в ноябре 1993 г. 15.6.3. Перегрузка шаблона функции Коль скоро существуют шаблоны функций, встает вопрос, как следует разрешать их перегрузку. Для данного средства допустимы только точные соответствия, а при разрешении перегрузки предпочтение отдается обычной функции с тем же именем: Разрешение перегрузки для шаблонов функций и других функций с тем же именем выполняется в три этапа [ARM]: □ поиск точного соответствия [ARM, § 13.2] среди функций; называние функции, если она найдена; □ нахождение шаблона функции, из которого можно инстанцировать функцию, точно соответствующую параметрам вызова; вызов этой функции, если шаблон найден; □ попытка применить обычную перегрузку [ARM, § 13.2] для функций; вызов функции, если она найдена. Если соответствие не найдено, вызов считается ошибкой. Если на первом шаге отыскивается более одного соответствия, то вызов неоднозначен и также считается ошибкой . Теперь такой подход кажется узкоспециализированны.м. Хотя он и работает, но служит почвой для .многих мелких сюрпризов и неприятностей. Уже в то вре.мя мне было ясно, что лучше как-то унифицировать правила для обычных функций и шаблонов. Но я не знал как. Вот приблизительный вариант альтернативного подхода, сформулированный Эндрю Кенигом: Для данного вызова найти множество функций, которые в принципе можно было бы подставить. В стандартном случае оно будет содержать функции, сгенерированные из разных шаблонов. Применить обычные правила разрешения к этому множеству функций . Такое решение позволило бы применять преобразования к аргументам шаблонов функций, и мы получили бы общую схему перегрузки для любых функций. Например: template<class Т> class В {/*...*/ }; template<class Т> class D : public В<Т> { /* ... */ }; template<class Т> void f(B<T>*); void g(B<int>* pb, D<int>* pd) { f(pb); f<int>(pb) f(pd); f<int>((B<int>*)pd); используется стандартное преобразование Это необходимо для того, чтобы шаблоны функций правильно взаимодействовали с наследованием. Другой пример: template<class Т> Т тах(Т,Т); const int s = 7;
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |