Wiem, że poniższy kod to częściowa specjalizacja klasy:
template <typename T1, typename T2>
class MyClass {
…
};
// partial specialization: both template parameters have same type
template <typename T>
class MyClass<T,T> {
…
};
Wiem też, że C ++ nie pozwala na częściową specjalizację szablonów funkcji (dozwolone jest tylko pełne). Ale czy mój kod oznacza, że częściowo wyspecjalizowałem mój szablon funkcji dla jednego / tego samego typu argumentów? Ponieważ działa z Microsoft Visual Studio 2010 Express! Jeśli nie, czy mógłbyś wyjaśnić koncepcję częściowej specjalizacji?
#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)
postanawia,max(T const&, T const&) [with T=int]
a niemax(T1 const&, T2 const&) [with T1=int and T2=int]
?Odpowiedzi:
Częściowa specjalizacja funkcji nie jest jeszcze dozwolona zgodnie ze standardem. W tym przykładzie faktycznie przeciążasz i nie specjalizujesz się w
max<T1,T2>
funkcji.Jego składnia powinna wyglądać trochę jak poniżej, gdyby była dozwolona:
// 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; }
W przypadku szablonów funkcji standard C ++ dopuszcza tylko pełną specjalizację - z wyłączeniem rozszerzeń kompilatora!
źródło
(T, T)
się(T1, T2)
na(int, int)
, to dlatego, że byli gwarantuje, że są 2 parametry i oba typy są takie same; ta ostatnia gwarantuje jedynie, że istnieją 2 parametry. Kompilator zawsze wybiera dokładny opis. np. jeśli musisz dokonać wyboru między dwoma opisami „rzeki”, który byś wybrał? „pobór wody” a „pobór wody płynącej”.Ponieważ częściowa specjalizacja jest niedozwolona - jak wskazywały inne odpowiedzi - możesz ją obejść, używając
std::is_same
istd::enable_if
, jak poniżej: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); }
Wynik:
$ ./a.out >>> messing with ints! works >>> messing with floats! 2
Edycja : w przypadku, gdy musisz być w stanie rozpatrzyć wszystkie pozostałe pozostałe przypadki, możesz dodać definicję, która stwierdza, że już potraktowane przypadki nie powinny pasować - w przeciwnym razie wpadniesz w niejednoznaczne definicje. Definicja może brzmieć:
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"); }
Który produkuje:
$ ./a.out >>> messing with ints! works >>> messing with floats! 2 >>> messing with unknown stuff! either
Chociaż to wszystko wygląda trochę nudno, ponieważ musisz powiedzieć kompilatorowi wszystko, co już zrobiłeś, całkiem wykonalne jest potraktowanie do 5 lub kilku dodatkowych specjalizacji.
źródło
Jeśli naprawdę chcesz zrozumieć szablony, powinieneś przyjrzeć się językom funkcjonalnym. Świat szablonów w C ++ jest swoim własnym językiem czysto funkcjonalnym.
W językach funkcjonalnych selekcji dokonuje się za pomocą dopasowania wzorców :
-- 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
Jak widać, przeciążamy definicję
isJust
.Cóż, szablony klas C ++ działają dokładnie w ten sam sposób. Podajesz główną deklarację, która określa liczbę i charakter parametrów. Może to być tylko deklaracja, lub też działać jako definicja (Twój wybór), a następnie możesz (jeśli chcesz) podać specjalizacje wzorca i skojarzyć z nimi inną (inaczej byłoby to głupio) wersję klasy .
W przypadku funkcji szablonów specjalizacja jest nieco bardziej niezręczna: nieco koliduje z rozwiązywaniem przeciążenia. W związku z tym zdecydowano, że specjalizacja będzie dotyczyć wersji niespecjalistycznej, a specjalizacje nie będą brane pod uwagę podczas rozwiązywania problemu z przeciążeniem. Dlatego algorytm wyboru odpowiedniej funkcji wygląda następująco:
(informacje na temat dogłębnego leczenia, patrz GotW # 49 )
Jako taka, szablonowa specjalizacja funkcji jest obywatelem drugiej strefy (dosłownie). Jeśli o mnie chodzi, byłoby nam lepiej bez nich: nie spotkałem się jeszcze z przypadkiem, w którym użycie specjalizacji szablonowej nie mogłoby zostać rozwiązane poprzez przeciążenie.
Nie, to po prostu przeciążenie i to jest w porządku. W rzeczywistości przeciążenia zwykle działają tak, jak się tego spodziewamy, podczas gdy specjalizacje mogą być zaskakujące (pamiętajcie o artykule w GotW, do którego dołączyłem).
źródło
"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."
A co z parametrami innymi niż szablonowe?boost::mpl::integral_c<unsigned, 3u>
. Innym rozwiązaniem mogłoby być użycieenable_if
/disable_if
, chociaż to już inna historia.Nieklasowa, niezmienna częściowa specjalizacja jest niedozwolona, ale jak powiedziano:
Dodanie klasy w celu przekazania wywołania funkcji może rozwiązać ten problem, oto przykład:
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, "");
źródło
Nie. Na przykład możesz prawnie specjalizować się
std::swap
, ale nie możesz prawnie zdefiniować własnego przeciążenia. Oznacza to, że nie możeszstd::swap
pracować dla własnego niestandardowego szablonu klasy.Przeciążenie i częściowa specjalizacja mogą w niektórych przypadkach mieć ten sam efekt, ale nie we wszystkich.
źródło
swap
przeciążenie w przestrzeni nazw.Spóźniona odpowiedź, ale niektórym spóźnionym czytelnikom może się to przydać: Czasami funkcja pomocnicza - zaprojektowana w taki sposób, że może być wyspecjalizowana - może również rozwiązać problem.
Wyobraźmy sobie więc, co próbowaliśmy rozwiązać:
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); }
OK, częściowa specjalizacja funkcji szablonu, nie możemy tego zrobić ... Więc „wyeksportuj” część potrzebną do specjalizacji do funkcji pomocniczej, wyspecjalizujmy ją i użyjmy:
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? }
Może to być interesujące, zwłaszcza jeśli alternatywy (normalne przeciążenia zamiast specjalizacji, obejście zaproponowane przez Rubensa, ... - nie to, że są złe lub moje jest lepsze, po prostu inna ) miałyby wspólny kod.
źródło