Подтвердить что ты не робот

Различия между специализацией шаблона и перегрузкой для функций?

Итак, я знаю, что есть разница между этими двумя лакомыми кусочками кода:

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;
}

Я смущен тем, что функциональные различия между этими двумя. Может ли кто-нибудь показать некоторые ситуации, когда эти фрагменты действуют иначе друг от друга?

4b9b3361

Ответ 1

Я могу только подумать о нескольких отличиях - вот несколько примеров, которые не обязательно причиняют вред (я думаю). Я опускаю определения, чтобы сохранить его кратким

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

Это потому, что специализации не найдены по поиску по имени, а по сопоставлению аргументов, поэтому декларация использования автоматически рассмотрит позднее введенную специализацию.

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

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 
  // preferred. 
  g(a);
}

Я рекомендую вам всегда использовать перегрузку, потому что она богаче (позволяет разрешить что-то вроде частичной специализации), и, кроме того, вы можете разместить функцию в любом пространстве имен, которое вы хотите (хотя тогда это не строго перегрузка). Например, вместо того, чтобы специализировать std::swap в пространстве имен std::, вы можете разместить перегрузку swap в своем собственном пространстве имен и сделать ее вызываемой ADL.

Независимо от того, что вы делаете, никогда не смешивайте специализацию и перегрузку, это будет чертовски беспорядочно, как в этой статье. В Стандарте есть прекрасный параграф об этом

Размещение явных деклараций специализации для шаблонов функций, шаблонов классов, функций-членов шаблонов классов, элементов статических данных шаблонов классов, классов-членов шаблонов классов, шаблонов классов-членов шаблонов классов, шаблонов-членов шаблонов классов, члена функции шаблонов-членов шаблонов классов, функции-члены шаблонов-членов классов, отличных от шаблонов, шаблоны-члены-члены классов-членов шаблонов классов и т.д., а также размещение объявлений частичной специализации шаблонов классов, шаблонов классов-членов не-шаблона классы, шаблоны классов членов шаблонов классов и т.д. могут повлиять на то, хорошо ли сформирована программа в соответствии с относительным позиционированием явных объявлений специализации и их точками инстанцирования в блоке трансляции, как указано выше и ниже. При написании специализации будьте осторожны с ее местоположением; или сделать его компиляцией, станет таким испытанием, чтобы разжечь его самосожжение.

Ответ 2

Специализация шаблонов является более общей, чем просто перегрузка. Вы можете специализировать такие вещи, как классы, а не просто простые функции. Перегрузка применяется только к функциям.

ОБНОВЛЕНИЕ: Чтобы уточнить больше комментариев AraK, вы действительно сравниваете яблоки и апельсины здесь. Перегрузка функции используется для представления возможности иметь разные функции совместно с одним именем, если у них разные подписи. Специализация шаблона используется для определения определенного фрагмента кода для определенного параметра типа. У вас не может быть специализация шаблона, если у вас нет шаблона. Если вы удалите первый фрагмент кода, который объявляет общий шаблон, вы получите ошибку времени компиляции, если попытаетесь использовать специализированную специализацию.

Итак, цель специализации шаблонов сильно отличается от перегрузки функции. Они просто ведут себя аналогично в вашем примере, в то время как они принципиально разные.

Если вы обеспечиваете перегрузку, вы объявляете независимый метод, который имеет одно и то же имя. Вы не препятствуете использованию шаблона с определенным параметром типа. Чтобы продемонстрировать этот факт, попробуйте:

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, если вы использовали специализированную специализацию.

Ответ 3

Один из таких примеров:

#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);
}

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

Ответ 4

AFAIK нет функциональной разницы. Все, что я могу добавить, это то, что если у вас есть как специализированная функция шаблона, так и обычная функция, то нет никакой неопределенности перегрузки, поскольку предпочтение отдается обычной функции.

Ответ 5

Только для того, чтобы подробно описать первый пункт, упомянутый в его . Специализации проверяются только после того, как разрешение перегрузки фактически выбрало первичный шаблон. Результат может привести к неожиданностям, когда функция перегружена и имеет явные специализации:

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 *)".

Только после этих шагов, когда выбрана лучшая функция, будут рассмотрены явные специализации выбранной функции. (В этом случае первичный № 2 не имеет ни одного, поэтому ни один не рассматривается).

Единственный способ вызвать вышеупомянутую явную специализацию здесь - фактически использовать явные аргументы шаблона в вызове:

void bar (int * i)
{
  foo<int*> (i);  // expliit template argument forces use of primary #1
}

Хорошим правилом является попытка избежать перегрузок, которые также явно специализированы, поскольку результирующие правила довольно сложны.