Я знаю, что приведенный ниже код является частичной специализацией класса:
template <typename T1, typename T2>
class MyClass {
…
};
// partial specialization: both template parameters have same type
template <typename T>
class MyClass<T,T> {
…
};
Также я знаю, что C ++ не допускает частичную специализацию шаблона функции (допускается только полная). Но означает ли мой код, что я частично специализировал свой шаблон функции для аргументов одного / того же типа? Потому что это работает для 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;
}
max(5,5)
разрешитьmax(T const&, T const&) [with T=int]
и нетmax(T1 const&, T2 const&) [with T1=int and T2=int]
?Ответы:
Частичная специализация функции пока не разрешена стандартом. В этом примере вы фактически перегружаете
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) { ^^^^^ <--- [supposed] specializing here return 10; }
В случае шаблонов функций стандартом C ++ разрешена только полная специализация , за исключением расширений компилятора!
источник
(T, T)
более(T1, T2)
для(int, int)
, потому , что прежние гарантии , что есть 2 параметра и оба типа одинаковы; последний только гарантирует, что есть 2 параметра. Компилятор всегда выбирает точное описание. например, если вам нужно сделать выбор между двумя описаниями «реки», какое из них вы бы выбрали? «сбор воды» vs «сбор текущей воды».Поскольку частичная специализация не допускается, как указывали другие ответы, вы можете обойти ее, используя
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 или еще нескольких специализаций.
источник
Если вы действительно хотите разбираться в шаблонах, вам следует взглянуть на функциональные языки. Мир шаблонов в C ++ - это отдельный чисто функциональный подъязык.
В функциональных языках выбор выполняется с помощью сопоставления по шаблону :
-- 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's None or Just isJust :: Maybe a -> Bool -- definition: two cases (_ is a wildcard) isJust None = False isJust Just _ = True
Как видите, мы перегружаем определение
isJust
.Что ж, шаблоны классов C ++ работают точно так же. Вы предоставляете основное объявление, в котором указывается количество и характер параметров. Это может быть просто объявление или также действовать как определение (по вашему выбору), а затем вы можете (если хотите) предоставить специализации шаблона и связать с ними другую (иначе было бы глупо) версию класса. .
Для шаблонных функций специализация несколько сложнее: она несколько конфликтует с разрешением перегрузки. Таким образом, было решено, что специализация будет относиться к неспециализированной версии, и специализации не будут учитываться при разрешении перегрузки. Таким образом, алгоритм выбора правильной функции становится:
(подробное описание см. в GotW # 49 )
Таким образом, шаблонная специализация функций является гражданином второй зоны (буквально). Насколько я понимаю, нам было бы лучше без них: я еще не сталкивался со случаем, когда использование специализации шаблона не могло быть решено с помощью перегрузки.
Нет, это просто перегрузка, и это нормально. Фактически, перегрузки обычно работают так, как мы от них ожидаем, в то время как специализации могут вызывать удивление (вспомните статью GotW, которую я связал).
источник
"As such, template specialization of functions is a second-zone citizen (literally). As far as I am concerned, we would be better off without them: I have yet to encounter a case where a template specialization use could not be solved with overloading instead."
Как насчет параметров шаблона без типа?boost::mpl::integral_c<unsigned, 3u>
. Другим решением может быть использованиеenable_if
/disable_if
, хотя это другая история.Неклассовая, неизменяемая частичная специализация не допускается, но, как сказано:
Добавление класса для пересылки вызова функции может решить эту проблему, вот пример:
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, "");
источник
Нет. Например, вы можете юридически специализироваться
std::swap
, но вы не можете юридически определить свою собственную перегрузку. Это означает, что вы не можете использоватьstd::swap
свой собственный шаблон класса.В некоторых случаях перегрузка и частичная специализация могут иметь одинаковый эффект, но далеко не во всех.
источник
swap
перегрузку в свое пространство имен.Поздний ответ, но некоторые поздние читатели могут счесть это полезным: иногда вспомогательная функция, разработанная так, чтобы ее можно было специализировать, тоже может решить проблему.
Итак, представим, что мы пытались решить:
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? }
Это может быть интересно, особенно если альтернативы (обычные перегрузки вместо специализаций, обходной путь, предложенный Рубенсом, ... - не то, чтобы они плохие или мои лучше, просто еще один) будут иметь довольно много общего кода.
источник