Различия между специализацией шаблонов и перегрузкой функций?
Итак, я знаю, что есть разница между этими двумя лакомыми кусочками кода:
template <typename T>
T inc(const T& t)
{
return t + 1;
}
template <>
int inc(const int& t)
{
return t + 1;
}
и
template <typename T>
T inc(const T& t)
{
return t + 1;
}
int inc(const int& t)
{
return t + 1;
}
Я понимаю, что функциональные различия между этими двумя. Может ли кто-то показать некоторые ситуации, когда эти фрагменты действуют по-разному друг от друга?
5 ответов
Я могу думать только о нескольких различиях-вот некоторые примеры, которые не обязательно наносят вред (я думаю). Я опускаю определения, чтобы сохранить его кратким
template <typename T> T inc(const T& t);
namespace G { using ::inc; }
template <> int inc(const int& t);
namespace G { void f() { G::inc(10); } } // uses explicit specialization
// --- against ---
template <typename T> T inc(const T& t);
namespace G { using ::inc; }
int inc(const int& t);
namespace G { void f() { G::inc(10); } } // uses template
это потому, что специализации не найдены путем поиска имени, но путем сопоставления аргументов, поэтому объявление using автоматически рассмотрит более позднюю введенную специализацию.
тогда вы, конечно, не можете частично специализировать шаблоны функций. Перегружать однако выполняет что-то очень похожие по частичному заказу (используя разные типы сейчас, чтобы сделать мою точку зрения)
template <typename T> void f(T t); // called for non-pointers
template <typename T> void f(T *t); // called for pointers.
int a;
void e() {
f(a); // calls the non-pointer version
f(&a); // calls the pointer version
}
это было бы невозможно с явной специализацией шаблона функции. Другой пример - когда задействованы ссылки, что заставляет вывод аргумента шаблона искать точное соответствие задействованных типов (отношения базового/производного класса по модулю и константа):
template<typename T> void f(T const &);
template<> void f(int * const &);
template<typename T> void g(T const &);
void g(int * const &);
int a[5];
void e() {
// calls the primary template, not the explicit specialization
// because `T` is `int[5]`, not `int *`
f(a);
// calls the function, not the template, because the function is an
// exact match too (pointer conversion isn't costly enough), and it's
// preferred.
g(a);
}
Я рекомендую вам всегда использовать перегрузку, потому что она богаче (позволяет что-то вроде частичная специализация позволит), и, кроме того, вы можете разместить функцию в любом пространстве имен, которое вы хотите (хотя тогда это больше не перегружает). Например, вместо того, чтобы специализироваться std::swap
на std::
пространство имен, вы можете разместить свой swap
перегрузка в вашем собственном пространстве имен и сделать его вызываемым ADL.
что бы вы ни делали,никогда не смешивайте специализацию и перегрузку, это будет адский беспорядок, как в этой статье очки из. В стандарте есть прекрасный абзац об этом
размещение явных объявлений специализации для шаблонов функций, шаблонов классов, функций-членов шаблонов классов, статических данных членов шаблонов классов, классов-членов шаблонов классов, шаблонов классов-членов шаблонов классов, шаблонов функций-членов шаблонов классов, функций-членов шаблонов классов, функций-членов шаблонов классов, шаблоны функций-членов классов-членов шаблонов классов и т. д., и размещение частичных объявлений специализации шаблонов классов, шаблонов классов-членов не-шаблонов классов, шаблонов классов-членов шаблонов классов и т.д., может повлиять на то, хорошо ли сформирована программа в соответствии с относительным расположением явных деклараций специализации и их точек создания экземпляров в блоке перевода, как указано выше и ниже. При написании специализации, будьте осторожнее с его местонахождением; или заставить его скомпилироваться будет таким испытанием, что зажжет его самосожжение.
специализация шаблонов является более общей, чем просто перегрузка. Вы можете специализировать такие вещи, как классы, а не просто простые функции. Перегрузка применяется только к функциям.
обновление: чтобы уточнить больше в комментарии Арака, вы действительно сравниваете яблоки и апельсины здесь. Перегрузка функций используется для введения возможности иметь разные функции с одним именем, если они имеют разные подписи. Специализация шаблона используется для определения конкретный фрагмент кода для конкретного типа параметра. У вас не может быть специализации шаблона, если у вас нет шаблона. При удалении первого фрагмента кода, объявляющего общий шаблон, при попытке использовать специализацию шаблона будет получена Ошибка времени компиляции.
таким образом, цель специализации шаблона довольно отличается от перегрузки функции. Они просто ведут себя одинаково в вашем примере, в то время как они принципиально отличаются.
если вы предоставьте перегрузку, вы объявляете независимый метод, который имеет то же имя. Вы не препятствуете использованию шаблона с определенным параметром типа. Чтобы продемонстрировать этот факт, попробуйте:
template <typename T>
T inc(const T& t)
{
return t + 1;
}
int inc(const int& t)
{
return t + 42;
}
#include <iostream>
int main() {
int x = 0;
x = inc<int>(x);
std::cout << "Template: " << x << std::endl; // prints 1.
x = 0;
x = inc(x);
std::cout << "Overload: " << x << std::endl; // prints 42.
}
как вы можете видеть, в этом примере есть два различных inc
функции int
значения: inc(const int&)
и inc<int>(const int&)
. Вы не можете развернуть общий шаблон, используя int
Если вы использовали шаблон специализации.
например:
#include <cstdio>
template <class T>
void foo(T )
{
puts("T");
}
//template <>
void foo(int*)
{
puts("int*");
}
template <class T>
void foo(T*)
{
puts("T*");
}
int main()
{
int* a;
foo(a);
}
фактически предлагается использовать не шаблонные перегрузки для функций и оставить специализацию для классов. Это обсуждается более подробно в Почему Бы Не Специализировать Шаблоны Функций?
AFAIK нет функциональной разницы. Все, что я могу добавить, это то, что если у вас есть как специализация функции шаблона, так и обычная функция, то нет двусмысленности перегрузки, поскольку обычная функция предпочтительна.
просто чтобы уточнить первый пункт, упомянутый litb в его ответ. Специализации проверяются только после того, как разрешение перегрузки фактически выбрало основной шаблон. Результат может привести к некоторым сюрпризам, когда функция перегружена и имеет явные специализации:
template <typename T> void foo (T); // Primary #1
template <> void foo<int*> (int*); // Specialization of #1
template <typename T> void foo (T*); // Primary #2
void bar (int * i)
{
foo(i);
}
при выборе функции для вызова выполняются следующие действия:
- поиск имени находит оба основных шаблона.
- каждый шаблон специализировано и разрешение перегрузки пытается выбрать лучшую функцию на основе преобразований между аргументами и параметрами.
- в этом случае нет никакой разницы в качестве преобразований.
- правила частичного заказа затем используются для выбора наиболее специализированного шаблона. В этом случае это второй паримар "foo (T*)".
только после этих шагов, когда была выбрана лучшая функция, явные специализации the избранные