Важность и необходимость специализации шаблона функции
Я прочитал C++ Primer, и там написано, что специализация шаблонов функций - это сложная тема, но я полностью потерян. Кто-нибудь может привести пример, почему специализация шаблона функции важна и необходима?
Почему шаблоны функций не поддерживают частичную специализацию, в то время как шаблоны классов поддерживают? Какова основная логика?
4 ответа
По сути, идея заключается в том, что вы можете писать шаблоны, которые ведут себя в общем случае для общего случая, но могут обрабатывать особые случаи. Одним из примеров использования специализации является std::vector
, std::vector<bool>
это специализация, которая упаковывает bool
элементы такие, что они используют только один бит на элемент, а не один байт. std::vector<T>
работает как обычный динамический массив для всех других типов.
Более сложное использование для специализации - метапрограммирование. Например, вот пример (из Википедии) о том, как использовать специализацию шаблонов для вычисления факториалов во время компиляции.
template <int N>
struct Factorial
{
enum { value = N * Factorial<N - 1>::value };
};
template <>
struct Factorial<0>
{
enum { value = 1 };
};
На ваш вопрос о том, почему функции не поддерживают частичную специализацию, можно ответить здесь. Код ниже показывает, как реализовать различные специализации.
template<typename T>
bool Less(T a, T b)
{
cout << "version 1 ";
return a < b;
}
// Function templates can't be partially specialized they can overload instead.
template<typename T>
bool Less(T* a, T* b)
{
cout << "version 2 ";
return *a < *b;
}
template<>
bool Less<>(const char* lhs, const char* rhs)
{
cout << "version 3 ";
return strcmp(lhs, rhs) < 0;
}
int a = 5, b = 6;
cout << Less<int>(a, b) << endl;
cout << Less<int>(&a, &b) << endl;
cout << Less("abc", "def") << endl;
Я не могу придумать пример, и я пытался почти с тех пор, как вы спросили. Как указал Джаганнатх, это давний совет не специализировать функции, а вместо этого перегружать их или использовать класс признаков (который может быть специализированным, даже частично специализированным).
Например, если вам нужно поменять местами два элемента, лучше полагаться на перегрузки (более предсказуемые и более расширяемые):
template<class T>
void f() {
T a, b;
using std::swap; // brings std::swap into scope as "fallback"
swap(a, b); // unqualified call (no "std::") so ADL kicks in
// also look at boost::swap
}
И как вы пишете своп для ваших типов:
// the cleanest way to do it for a class template:
template<class T>
struct Ex1 {
friend void swap(Ex1& a, Ex1& b) { /* do stuff */ }
};
// you can certainly place it outside of the class instead---but in the
// same namespace as the class---if you have some coding convention
// against friends (which is common, but misguided, IMHO):
struct Ex2 {};
void swap(Ex2& a, Ex2& b) { /* do stuff */ }
Оба из которых позволяют Argument Dependent Lookup (ADL).
Другие функции, такие как stringify/str или repr (представление), могут также не быть членами и использовать преимущества ADL через перегрузку:
struct Ex3 {
friend std::string repr(Ex3 const&) { return "<Ex3 obj>"; }
};
std::string repr(bool b) { return b ? "true" : "false"; }
// possible fallback:
template<class T>
std::string repr(T const& v) {
std::ostringstream out;
out << v;
return out.str();
}
// but in this particular case, I'd remove the fallback and document that
// repr() must be overloaded appropriately before it can be used with a
// particular type; for other operations a default fallback makes sense
Если взглянуть на это иначе, было бы неплохо, если бы шаблоны функций могли служить в качестве реестра для конкретных реализаций, но из-за ограничений (в нынешнем C++, не уверенных в точности, что приносит C++0x), они не работают так же хорошо в качестве перегрузки или шаблонов классов для этой цели реестра.
Есть одно использование, которое удобно, но не важно: легко определить определенные специализации в отдельной библиотеке, возможно, в общей библиотеке (.so или.dll). Это удобно, потому что требует минимальных изменений в универсальном шаблоне, но не важно, потому что это кажется мне редким (в дикой природе, и, конечно, редким в моем опыте), и разработчики все еще могут использовать либо перегрузку, либо пересылку в полностью специализированный класс Неспециализированный метод шаблона.
Чтобы проиллюстрировать, почему важна специализация шаблона функции, рассмотрим std::swap
шаблонная функция. По умолчанию, std::swap(x, y)
по сути делает:
T temp = x;
x = y;
y = temp;
но это может быть неэффективно, так как это требует создания дополнительной копии x
и может сделать дополнительное копирование в назначениях. Это особенно плохо, если x
большой (например, если это std::vector
со многими элементами). Кроме того, каждая из вышеперечисленных строк может завершиться сбоем и вызвать исключения, потенциально оставляя x
а также y
в плохих, противоречивых состояниях.
Для решения этой проблемы многие классы предоставляют свои swap
методы (в том числе std::vector
) вместо этого поменяйте местами указатели на их внутренние данные. Это более эффективно и может быть гарантировано никогда.
Но теперь у вас есть случай, когда вы можете использовать std::swap(x, y)
на некоторые типы, но нужно позвонить x.swap(y)
на других типах. Это сбивает с толку, и это плохо для шаблонов, поскольку они не смогут поменять местами два общих и согласованных способа.
Но std::swap
может быть специализированным, чтобы он вызывал x.swap(y)
когда вызывается на определенные типы. Это означает, что вы можете использовать std::swap
везде и (надеюсь) ожидать, что это будет хорошо себя вести.