2009-10-02 5 views
20

Tak, wiem, że istnieje różnica między tymi dwoma ciekawostki kodu:Różnice między specjalizacją szablonu a przeciążaniem dla funkcji?

template <typename T> 
T inc(const T& t) 
{ 
    return t + 1; 
} 

template <> 
int inc(const int& t) 
{ 
    return t + 1; 
} 

i

template <typename T> 
T inc(const T& t) 
{ 
    return t + 1; 
} 

int inc(const int& t) 
{ 
    return t + 1; 
} 

Jestem mylić, co funkcjonalne różnice pomiędzy tymi dwoma są. Czy ktoś może pokazać sytuacje, w których te fragmenty działają inaczej od siebie?

+0

Przegłosowałem pytanie, ponieważ jest to jeden z ciemnych obszarów w C++ (przynajmniej dla mnie)! – AraK

+5

O co konkretnie prosisz? Tylko niektóre różnice lub niektóre przypadki, w których występuje zagrożenie? Na przykład, 'inc <> (10);' zachowuje się inaczej dla tych dwóch fragmentów - ale czy to właśnie chcesz usłyszeć? –

Odpowiedz

14

Mogę tylko wymyślić kilka różnic - oto kilka przykładów, które niekoniecznie powodują szkody (myślę). Ja pomijając definicje aby go terse

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 

To dlatego, że specjalizacje nie są uznane przez wyszukiwania nazw, ale dopasowując argumentów, a więc za pomocą deklaracja zostanie automatycznie rozważyć później wprowadził specjalizację.

Następnie, oczywiście, nie można częściowo specjalizować szablonów funkcji.Przeciążenie jednak realizuje coś bardzo podobnego przez częściowego uporządkowania (za pomocą różnych typów teraz, aby mój punkt)

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 
} 

to nie byłoby możliwe z szablonu funkcja wyraźnej specjalizacji. Innym przykładem jest, gdy zaangażowane są referencje, które powoduje odliczenie szablon argument szukać dokładnego dopasowania typów zaangażowanych (baza modulo/pochodzące klasy relacje i constness):

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

polecam, aby zawsze używać przeciążenie, ponieważ jest bogatsze (pozwala na to coś takiego jak częściowa specjalizacja), a ponadto możesz umieścić funkcję w dowolnej przestrzeni nazw (choć wtedy nie jest to już nadmierne przeciążanie). Na przykład zamiast konieczności wyspecjalizowania std::swap w przestrzeni nazw std::, można umieścić przeciążenie swap we własnym obszarze nazw i spowodować, aby był on wywoływalny przez ADL.

Cokolwiek robisz, nigdy nie mieszaj specjalizacji i przeciążaj, będzie to piekielnie brzydki jak this article wskazuje. Standard zawiera piękny akapit na temat tego, co to jest:

Umieszczenie deklaracji specjalizacji w szablonach funkcji, szablonach klas, funkcjach składowych szablonów klas, statycznych elementach danych szablonów klas, klasach członków szablonów klas, szablonach klas członków szablony klas, szablony funkcji członkowskich szablonów klas, funkcje składowe szablonów członków szablonów klas, funkcje składowe szablonów członków klas innych niż szablony, szablony funkcji członkowskich klas elementów szablonów klas itp. oraz umieszczanie deklaracji częściowej specjalizacji szablonów klas, szablonów klas członków klas bez szablonów, szablonów klas członkowskich szablonów klas itp., może wpływać na to, czy program jest dobrze uformowany zgodnie z relatywnym pozycjonowaniem jawnej decyzy specjalizacji aranżacje i ich punkty stawiania w jednostce tłumaczeniowej jak określono powyżej i poniżej. Pisząc specjalizację, uważaj na jej lokalizację; lub sprawić, że będzie on się kompilował, będzie próbą rozpalenia jej samopodpalenia.

+0

Świetny artykuł. . – rlbond

+0

+1 miły - podoba mi się :) –

+0

dzięki ludziom :) –

5

Specjalizacja szablonów jest bardziej ogólna niż tylko przeciążenie. Możesz specjalizować takie rzeczy jak zajęcia, a nie tylko proste funkcje. Przeciążanie dotyczy tylko funkcji.

AKTUALIZACJA: Aby wyjaśnić więcej na komentarz AraK, naprawdę porównujesz tutaj jabłka i pomarańcze. Przeciążanie funkcji jest używane, aby wprowadzić możliwość posiadania różnych funkcji o wspólnej nazwie, jeśli mają one różne podpisy. Specjalizacja szablonów służy do definiowania określonego fragmentu kodu dla określonego parametru typu. Nie możesz mieć specjalizacji szablonu, jeśli nie masz szablonu. Jeśli usuniesz pierwszy fragment kodu, który deklaruje ogólny szablon, otrzymasz błąd czasu kompilacji, jeśli spróbujesz użyć specjalizacji szablonu.

Cel specjalizacji szablonów różni się znacznie od przeciążenia funkcji. Po prostu zachowują się podobnie w twoim przykładzie, podczas gdy są zasadniczo odmienne.

Jeśli podajesz przeciążenie, deklarujesz niezależną metodę, która ma tę samą nazwę. Nie uniemożliwiasz używania szablonu z określonym parametrem typu. Aby wykazać ten fakt, spróbuj:

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

Jak widać, w tym przypadku istnieją dwa odrębne inc funkcje int wartości: inc(const int&) i inc<int>(const int&). Nie można rozwinąć ogólnego szablonu przy użyciu int, jeśli korzystałeś ze specjalizacji szablonu.

+2

Dobra uwaga, chociaż nie odpowiada na pytanie. – AraK

+1

AraK: Pytanie, jak rozumiem, jest w zasadzie, dlaczego istnieją dwa sposoby osiągnięcia tego samego. Wykazuje podobny wynik uzyskany za pomocą dwóch metod i prosi o różnicę między tymi dwoma metodami. Moja odpowiedź jest taka, że ​​te dwie metody odnoszą się do różnych kontekstów, a pojedynczy przykład, w którym te dwie mają zastosowanie, nie czyni ich odpowiednikami. –

+0

Powiedziałem, że to dobra uwaga, ale nie odpowiada różnicy w funkcjonalności pomiędzy * specjalizacją szablonu funkcji * i * przeciążaniem funkcji *. Mówisz ogólnie o specjalizacji szablonów. Myślę, że tytuł jest trochę mylący. – AraK

1

AFAIK nie ma różnicy funkcjonalnej. Wszystko, co mogę dodać, to to, że jeśli masz zarówno specjalizację funkcji szablonu, jak i zwykłą funkcję zdefiniowaną, nie ma przeciążenia dwuznacznego, ponieważ funkcja zwykła jest faworyzowana.

4

Jednym z takich przykładów:

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

To jest rzeczywiście sugeruje, że używasz przeciążeń non-szablonów dla funkcji i pozostawić specjalizacji dla klas. To jest dyskutowane o większej długości w Why Not Specialize Function Templates?

1

Wystarczy opracować na pierwszym punkcie wspomnianym przez litb w jego answer. Specjalizacje są sprawdzane tylko wtedy, gdy rozdzielczość przeciążenia faktycznie wybrała szablon podstawowy. W rezultacie może prowadzić do pewnych niespodzianek gdzie funkcja jest przeciążony i ma wyraźne specjalizacje:

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

Wybierając które funkcjonują zadzwonić następujące etapy odbywają się:

  1. Nazwa odnośnika znajdzie zarówno szablony pierwotnych.
  2. Każdy szablon jest wyspecjalizowany, a rozdzielczość przeciążania próbuje wybrać najlepszą funkcję na podstawie konwersji między argumentami i parametrami.
  3. W tym przypadku nie ma różnicy w jakości konwersji.
  4. Częściowe reguły porządkowania są następnie używane do wyboru najbardziej wyspecjalizowanego szablonu. W tym przypadku jest to drugi paradise "foo (T *)".

Dopiero po tych schodach, kiedy najlepszy funkcja została wybrana będzie jawne specjalizacje funkcji wybrany rozpatrywane. (W tym przypadku podstawowa nr 2 nie ma, więc żadna nie jest brana pod uwagę).

Jedynym sposobem, aby połączyć się z powyższą wyraźną specjalizację tutaj, jest faktycznie korzysta wyraźne argumenty szablonu w zaproszeniu:

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

Dobrą zasadą jest, aby spróbować uniknąć przeciążeń, które są także wyraźnie określony wyspecjalizowanym , ponieważ reguły wynikowe są dość skomplikowane.

Powiązane problemy