Частичная специализация шаблона функций С++?

Я знаю, что приведенный ниже код является частичной специализацией класса:

template <typename T1, typename T2> 
class MyClass { 
  … 
}; 


// partial specialization: both template parameters have same type 
template <typename T> 
class MyClass<T,T> { 
  … 
}; 

Также я знаю, что С++ не разрешает частичную специализацию шаблона шаблона (допускается только полнота). Но мой код означает, что я частично специализировал свой шаблон функции для аргументов одного и того же типа? Потому что он работает для Microsoft Visual Studio 2010 Express! Если нет, то не могли бы вы объяснить концепцию частичной специализации?

#include <iostream>
using std::cin;
using std::cout;
using std::endl;

template <typename T1, typename T2> 
inline T1 max (T1 const& a, T2 const& b) 
{ 
    return a < b ? b : a; 
} 

template <typename T> 
inline T const& max (T const& a, T const& b)
{
    return 10;
}


int main ()
{
    cout << max(4,4.2) << endl;;
    cout << max(5,5) << endl;
    int z;
    cin>>z;
}

Ответ 1

В этом примере вы фактически перегружаете (не специализируетесь) функцию max<T1,T2>. Синтаксис частичной специализации должен выглядеть несколько ниже (если бы это было разрешено):

//Partial specialization is not allowed by the spec, though!
template <typename T> 
inline T const& max<T,T> (T const& a, T const& b)
{                  ^^^^^ <--- specializing here
    return 10;
}

[Примечание: в случае шаблона функции только стандартная полная разрешена стандартом С++ (исключая расширения компилятора).]

Ответ 2

Так как частичная специализация не допускается - как указывали другие ответы - вы могли бы обойти ее с помощью std::is_same и std::enable_if, как показано ниже:

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, int>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with ints! " << f << std::endl;
}

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, float>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with floats! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
}

Вывод:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2

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

template <typename T, class F>
inline typename std::enable_if<(not std::is_same<T, int>::value)
    and (not std::is_same<T, float>::value), void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with unknown stuff! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
    typed_foo<std::string>("either");
}

Что производит:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2
>>> messing with unknown stuff! either

Хотя это все дело выглядит немного скучно, так как вы должны рассказать компилятору все, что вы уже сделали, вполне можно обработать до 5 или нескольких специальностей.

Ответ 3

Что такое специализация?

Если вы действительно хотите понять шаблоны, вы должны взглянуть на функциональные языки. Мир шаблонов в С++ является чисто функциональным подъязыком.

В функциональных языках выбор осуществляется с помощью сопоставления с образцом:

-- An instance of Maybe is either nothing (None) or something (Just a)
-- where a is any type
data Maybe a = None | Just a

-- declare function isJust, which takes a Maybe
-- and checks whether it None or Just
isJust :: Maybe a -> Bool

-- definition: two cases (_ is a wildcard)
isJust None = False
isJust Just _ = True

Как вы можете видеть, мы перегружаем определение isJust.

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

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

  • Выполнение разрешения перегрузки среди обычных функций и неспециализированных шаблонов
  • Если выбран неспециализированный шаблон, проверьте, существует ли для него специализация, которая будет лучше соответствовать

(для углубленного изучения см. GotW # 49)

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

Является ли эта специализация шаблоном?

Нет, это просто перегрузка, и это нормально. Фактически, перегрузки обычно работают так, как мы их ожидаем, в то время как специализации могут быть неожиданными (вспомните связанную с GotW статью).

Ответ 4

Нет. Например, вы можете юридически специализировать std::swap, но вы не можете юридически определить свою собственную перегрузку. Это означает, что вы не можете заставить std::swap работать для собственного шаблона пользовательского класса.

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

Ответ 5

Неклассическая, непеременная частичная специализация не допускается, но, как сказано:

Все проблемы в информатике могут быть решены другим уровнем косвенности. - Дэвид Уилер

Добавление класса для пересылки вызова функции может решить это, вот пример:

template <class Tag, class R, class... Ts>
struct enable_fun_partial_spec;

struct fun_tag {};

template <class R, class... Ts>
constexpr R fun(Ts&&... ts) {
  return enable_fun_partial_spec<fun_tag, R, Ts...>::call(
      std::forward<Ts>(ts)...);
}

template <class R, class... Ts>
struct enable_fun_partial_spec<fun_tag, R, Ts...> {
  constexpr static R call(Ts&&... ts) { return {0}; }
};

template <class R, class T>
struct enable_fun_partial_spec<fun_tag, R, T, T> {
  constexpr static R call(T, T) { return {1}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, int> {
  constexpr static R call(int, int) { return {2}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, char> {
  constexpr static R call(int, char) { return {3}; }
};

template <class R, class T2>
struct enable_fun_partial_spec<fun_tag, R, char, T2> {
  constexpr static R call(char, T2) { return {4}; }
};

static_assert(std::is_same_v<decltype(fun<int>(1, 1)), int>, "");
static_assert(fun<int>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 1)), char>, "");
static_assert(fun<char>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<long>(1L, 1L)), long>, "");
static_assert(fun<long>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<double>(1L, 1L)), double>, "");
static_assert(fun<double>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<int>(1u, 1)), int>, "");
static_assert(fun<int>(1u, 1) == 0, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 'c')), char>, "");
static_assert(fun<char>(1, 'c') == 3, "");

static_assert(std::is_same_v<decltype(fun<unsigned>('c', 1)), unsigned>, "");
static_assert(fun<unsigned>('c', 1) == 4, "");

static_assert(std::is_same_v<decltype(fun<unsigned>(10.0, 1)), unsigned>, "");
static_assert(fun<unsigned>(10.0, 1) == 0, "");

static_assert(
    std::is_same_v<decltype(fun<double>(1, 2, 3, 'a', "bbb")), double>, "");
static_assert(fun<double>(1, 2, 3, 'a', "bbb") == 0, "");

static_assert(std::is_same_v<decltype(fun<unsigned>()), unsigned>, "");
static_assert(fun<unsigned>() == 0, "");

Ответ 6

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

Итак, представьте себе, это то, что мы пытались решить:

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = new R(x);
    f(r, y); // another template function?
}

// for some reason, we NEED the specialization:
template <typename R, typename Y>
void function<R, int, Y>(int x, Y y) 
{
    // unfortunately, Wrapper has no constructor accepting int:
    Wrapper* w = new Wrapper();
    w->setValue(x);
    f(w, y);
}

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

template <typename R, typename T>
R* create(T t)
{
    return new R(t);
}
template <>
Wrapper* create<Wrapper, int>(int n) // fully specialized now -> legal...
{
    Wrapper* w = new Wrapper();
    w->setValue(n);
    return w;
}

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = create<R>(x);
    f(r, y); // another template function?
}

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