#cpp
Здравствуйте. Есть шаблон для обобщенного типа данных: templatevoid Swap(T &, T &); Специализация используется, если для какого-то конкретного типа данных (скажем int) нужно использовать иную реализацию функции. template <> void Swap (int &, int &); Но того же можно добиться определив обычную функцию void Swap(int &, int &); Зачем тогда нужны явные специализации. Приведите пожалуйста пример невозможности использования обычной функции и необходимости использования, преимущества явной специализации.
Ответы
Ответ 1
Для шаблонов функций явная специализация шаблона и обычная перегрузка нешаблонной функцией - это два альтернативных способа задания частной версии функции, как вы сами заметили. Обычно рекомендуется для этой цели пользоваться именно вторым вариантом - перегрузкой, а явную специализацию не трогать. Явная специализация предназначена в первую очередь для шаблонов классов, а не функций. Именно на эту тему есть статья Herb Sutter: http://www.gotw.ca/publications/mill17.htm Суть тут в том, что перегруженные функции, как шаблонные так и нешаблонные, непосредственно видны процессу overload resolution, а вот явные специализации шаблонов в процессе overload resolution не участвуют вообще. В overload resolution участвуют только главные (базовые) шаблоны, а их явные специализации изначально тихонько стоят в сторонке. Они будут рассматриваться только позже, и только те, чей главный (базовый) шаблон "победит" в процессе overload resolution. К сожалению, соответствие между базовыми шаблонами и явными специализациями может зависеть от порядка их объявления, что может вести к неприятным и неожиданным эффектам. В частности приводится такой пример templatevoid foo(T); // 1: базовый шаблон template void foo(T *); // 2: еще один, перегруженный базовый шаблон template <> void foo<>(int *); // 3: явная специализация для базового шаблона 2 int *p; foo(p); В этом случае, по вышеупомянутым правилам языка в процессе overload resolution участвуют только базовые шаблоны 1 и 2 (но не специализация 3). Побеждает шаблон 2. Только после этого во внимание принимаются возможные специализации этого шаблона. В результате выбирается явная специализация 3 и вызывается вариант 3. А теперь просто поменяем местами объявления 2 и 3 template void foo(T); // 1: базовый шаблон template <> void foo<>(int *); // 3: явная специализация для базового шаблона 1 template void foo(T *); // 2: еще один, перегруженный базовый шаблон int *p; foo(p); Теперь, при таком порядке объявления, явная специализация 3 привязывается уже к базовому шаблону 1 (!), а не 2. Далее в процессе overload resolution, как и прежде, участвуют только базовые шаблоны 1 и 2. Побеждает, как и прежде, шаблон 2. Но теперь явная специализация 3 уже не принимается во внимание и не выбирается, ибо она теперь "принадлежит" шаблону 1. В результате вызывается вариант 2. Как видите, как будто невинная перестановка объявлений серьезно поменяла семантику кода. Чтобы этого не происходило, рекомендуется для шаблонов функций применять механизм перегрузки, а не явной специализации. Это позволит частным версиям функции участвовать в процессе overload resolution сразу и самостоятельно, а не прятаться за широкой спиной своего базового шаблона с туманными перспективами. Ответ 2
Ответ AnT'а могу обобщить и дополнить следующим: Сначала выбирается "лучший" кандидат среди обычных функций и базовых шаблонов, причем предпочтение отдается обычным функциям. Если же более подходящим является базовый шаблон, то проверяется "а нет ли у него еще более подходящих специализаций" и, если таковые существуют (причем чтобы специализация шаблонной функции считалась таковой, базовый шаблон должен быть объявлен в коде перед этой специализацией), то выбирается одна из них. Контрольный пример: templatevoid func(T parm); template <> void func (int parm){} void func(int parm){} // вызовется именно эта функция void main() { func(0); } Также начиная с C++11 шаблоны удобно использовать для рекурсивных вычислений причем в связке с обычными функциями. Стало куда удобнее писать функции с переменным числом параметров (в отличие от аналогичного формата с переменным числом параметров, оставшегося со времен C). Правда остались некоторые ограничения, например, тип Types... - это "пакет параметров" шаблона, получать параметры из которого можно только последовательно (т.е. например, получить параметр из пакета по его индексу невозможно). Например, вычисление суммы неопределенного числа аргументов: int calcSum(int parm) { return parm; } template int calcSum(int parm1, Args... args) { return parm1 + calcSum(args...); } void main() { int sum = calcSum(1, 2, 3, 4); } Или даже так: template void ignore(T...) {} template int calcSum(T... parms) { int result = 0; ignore(result += parms...); return result; } void main() { int sum = calcSum(1, 2, 3, 4); } Раньше же приходилось не хило так поизвращаться с функциями вида: void calcSum(int* parm, ...) { // передвигаться по параметрам в стиле: ++parm; // т.е. крайне желательно, чтобы все параметры имели один тип } Ответ 3
Имитация перегрузки функций по возвращаемому значению templateT get_value(); template<> int get_value() { return 42; } template<> double get_value() { return 3.14; } Использование auto x = get_value (); auto y = get_value (); Однако, если использовать с типом не имеющим специализации, обшибку выдаст лишь линкер, но не компилятор. Но можно определить обобщенную функцию так template T get_value() { static_assert(0, "no specialization for this type provided"); } это позволит получить ошибку во время компиляции.
Комментариев нет:
Отправить комментарий