Важность и необходимость специализации шаблона функции - PullRequest
6 голосов
/ 04 февраля 2010

Я читал C ++ Primer, и там говорится, что специализация на шаблонах функций - это сложная тема, но я полностью растерялся. Кто-нибудь может привести пример, почему специализация шаблона функции важна и необходима?

Почему шаблоны функций не поддерживают частичную специализацию, в то время как шаблоны классов поддерживают? Какова основная логика?

Ответы [ 4 ]

8 голосов
/ 04 февраля 2010

На ваш вопрос, почему функции не поддерживают частичную специализацию, можно ответить здесь . Код ниже показывает, как реализовать различные специализации.

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 голосов
/ 04 февраля 2010

Я не могу вспомнить пример, и я пытался почти с тех пор, как вы спросили. Как указывает Джаганнатха , давний совет давал совет не специализировать функции, а перегрузить их или использовать класс черт (который может быть специализированным, даже частично специализированный).

Например, если вам нужно поменять местами два элемента, лучше полагаться на перегрузки (более предсказуемо и более расширяемо):

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

Если взглянуть на это иначе, было бы неплохо, если бы шаблоны функций могли служить реестром для конкретных реализаций, но из-за ограничений (в текущем C ++ не совсем точно известно, что дает C ++ 0x здесь) они не работают так же хорошо, как перегрузка или шаблоны классов для этой цели реестра.

Есть одно удобное использование, но не важно : простое определение определенных специализаций для отдельной библиотеки, возможно общей библиотеки (.so или .dll). Это удобно, потому что требует минимальных изменений в универсальном шаблоне, но не важно, потому что оно кажется мне редким (в дикой природе, и, конечно, редким в моем опыте), и разработчики все еще могут использовать либо перегрузку, либо пересылку в полностью специализированный класс Неспециализированный метод шаблона.

1 голос
/ 04 февраля 2010

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

0 голосов
/ 04 февраля 2010

По сути, идея заключается в том, что вы можете писать шаблоны, которые ведут себя в общем случае для общего случая, но могут обрабатывать особые случаи.Одним из примеров использования специализации является 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 };
};
...