Важность и необходимость специализации шаблона функции
Я читал С++ Primer, и он говорит, что специализация шаблонов функций - это расширенная тема, но я полностью потерян. Может ли кто-нибудь предложить пример, почему специализация шаблонов функций важна и необходима?
Почему функциональные шаблоны не поддерживают частичную специализацию, а шаблоны классов? Какова основная логика?
Ответы
Ответ 1
В принципе, идея состоит в том, что вы можете писать шаблоны, которые ведут общий путь для общего случая, но могут обрабатывать особые случаи. Один пример использования специализации - 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 };
};
Ответ 2
На ваш вопрос о том, почему функции не поддерживают частичную специализацию, можно ответить здесь. В приведенном ниже коде показано, как реализовать различные специализации.
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;
Ответ 3
Я не могу придумать пример, и я пытался с тех пор, как вы спросили. Как указано в Jagannath, это был давний совет, чтобы не специализировать функции, но вместо этого перегружать их или использовать класс признаков (который может быть специализированным, даже частично специализированным).
Например, если вам нужно поменять два элемента, то лучше полагаться на перегрузки (более предсказуемым и более расширяемым):
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 */ }
Оба из них позволяют Поиск зависимых аргументов (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
Чтобы посмотреть на это по-другому, было бы неплохо, если бы функциональные шаблоны могли служить реестром для конкретных реализаций, но из-за ограничений (в текущем С++, не совсем точно, что С++ 0x приносит сюда), они не работа, а также перегрузка или шаблоны классов для этой цели реестра.
Существует одно использование, которое удобно, но не важно: легко определить определенные специализации в отдельной библиотеке, возможно, в общей библиотеке (.so или .dll). Это удобно, потому что это требует минимальных изменений в общем шаблоне, но не важно, потому что это кажется мне редким (в дикой природе, и, конечно, редко в моем опыте), и разработчики могут по-прежнему использовать либо перегрузку, либо пересылку в полностью специализированный класс шаблон неспециализированный метод.
Ответ 4
Чтобы проиллюстрировать, почему специализация шаблона функции важна, рассмотрите функцию шаблона std::swap
. По умолчанию std::swap(x, y)
выполняет:
T temp = x;
x = y;
y = temp;
но это может быть неэффективным, поскольку оно предполагает создание дополнительной копии x
и может выполнять дополнительное копирование в назначениях. Это особенно плохо, если x
велико (например, если a std::vector
со многими элементами). Кроме того, каждая из вышеперечисленных строк может терпеть неудачу и бросать исключения, потенциально оставляя x
и y
в плохих, несогласованных состояниях.
Чтобы решить эту проблему, многие классы предоставляют свои собственные методы swap
(включая std::vector
), которые вместо этого заменяют указатели на их внутренние данные. Это более эффективно и может быть гарантировано никогда не сбой.
Но теперь у вас есть случай, когда вы можете использовать std::swap(x, y)
для некоторых типов, но вам нужно вызвать x.swap(y)
для других типов. Это запутывает, и это плохо для шаблонов, поскольку они не смогут обменять два объекта общим, последовательным способом.
Но std::swap
может быть специализированным, чтобы он вызывал x.swap(y)
при вызове определенных типов. Это означает, что вы можете использовать std::swap
всюду и (надеюсь) ожидать, что он будет хорошо себя вести.