C ++ specjalizacja częściowa szablon funkcji?


87

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

Poszukaj analogii specjalizacji klasowej. Jeśli nazywa się to specjalizacją klas, to dlaczego miałbym rozważyć to samo dla funkcji, co przeciążanie?
— Narek

1
Nie, składnia specjalizacji jest inna. Spójrz na (rzekomą) składnię specjalizacji funkcji w mojej odpowiedzi poniżej.
— iammilind

2
Dlaczego nie powoduje to błędu „Call to max is ambigious”? Jak max(5,5)postanawia, max(T const&, T const&) [with T=int]a nie max(T1 const&, T2 const&) [with T1=int and T2=int]?
— NHDaly

Odpowiedzi:


81

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!


1
@Narek, Częściowa specjalizacja funkcji nie jest częścią standardu (z jakichkolwiek powodów). Myślę, że MSVC obsługuje to jako rozszerzenie. Może być po jakimś czasie, na to pozwolą również inne kompilatory.
— iammilind

1
@iammilind: Nie ma problemu. Wydaje się, że już to wie. Dlatego próbuje tego również dla szablonu funkcji. Więc zredagowałem to ponownie, teraz wyjaśniając.
— Nawaz

20
Czy ktoś może wyjaśnić, dlaczego częściowa specjalizacja jest niedozwolona?
— HelloGoodbye

2
@NHDaly, nie daje błędu niejednoznaczności, ponieważ 1 funkcja jest lepiej dopasowana niż druga. Dlaczego wybiera (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”.
— iammilind

1
@kfsone, myślę, że ta funkcja jest w trakcie sprawdzania, stąd otwarta do interpretacji. Możesz odnieść się do tej sekcji open-std , którą widziałem w Dlaczego standard C ++ nie zezwala na częściową specjalizację szablonów funkcji?
— iammilind

44

Ponieważ częściowa specjalizacja jest niedozwolona - jak wskazywały inne odpowiedzi - możesz ją obejść, używając std::is_samei std::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.


Naprawdę nie ma takiej potrzeby, ponieważ można to obsłużyć przez przeciążenie funkcji w znacznie prostszy i wyraźniejszy sposób.
— Adrian

2
@Adrian Naprawdę nie mogę wymyślić żadnego innego podejścia do przeciążania funkcji, aby rozwiązać ten problem. Zauważyłeś, że częściowe przeciążenie jest niedozwolone, prawda? Podziel się z nami swoim rozwiązaniem, jeśli uważasz, że jest bardziej zrozumiałe.
— Rubens,

1
czy jest jakiś inny sposób na łatwe złapanie wszystkich funkcji szablonu?
— Nick

15

Co to jest specjalizacja?

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:

  1. Rozpoznawanie przeciążenia między zwykłymi funkcjami i niespecjalistycznymi szablonami
  2. Jeśli wybierzesz niespecjalizowany szablon, sprawdź, czy istnieje dla niego specjalizacja, która byłaby lepiej dopasowana

(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.

Czy to specjalizacja dotycząca szablonów?

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


"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?
— Jules GM

@Julius: nadal możesz używać przeciążania, aczkolwiek wprowadzając parametr fikcyjny, taki jak boost::mpl::integral_c<unsigned, 3u>. Innym rozwiązaniem mogłoby być użycie enable_if/ disable_if, chociaż to już inna historia.
— Matthieu M.

7

Nieklasowa, niezmienna częściowa specjalizacja jest niedozwolona, ​​ale jak powiedziano:

Wszystkie problemy w informatyce można rozwiązać na innym poziomie pośrednictwa. —— David Wheeler

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, "");

4

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żesz std::swappracować 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.


4
Dlatego umieszczasz swapprzeciążenie w przestrzeni nazw.
— jpalecek

2

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.