Важность и необходимость специализации шаблона функции

Я прочитал 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 везде и (надеюсь) ожидать, что это будет хорошо себя вести.

Другие вопросы по тегам