26.3 -- Specjalizacja szablonu funkcji

Podczas tworzenia instancji szablonu funkcji dla danego typu kompilator wycina kopię funkcji z szablonem i zastępuje parametry typu szablonu rzeczywistymi typami używanymi w deklarację zmiennej. Oznacza to, że konkretna funkcja będzie miała te same szczegóły implementacji dla każdego typu instancji (tylko przy użyciu różnych typów). Chociaż w większości przypadków jest to dokładnie to, czego chcesz, czasami zdarzają się przypadki, w których przydatne jest nieco inne zaimplementowanie funkcji szablonowej dla określonego typu danych.

Korzystanie z funkcji innej niż szablon

Rozważ następujący przykład:

#include <iostream>

template <typename T>
void print(const T& t)
{
    std::cout << t << '\n';
}

int main()
{
    print(5);
    print(6.7);
    
    return 0;
}

Wypisuje:

5
6.7

Załóżmy teraz, że chcemy, aby w notacji naukowej były wyświetlane podwójne wartości (i tylko podwójne wartości).

Jednym ze sposobów uzyskania innego zachowania dla danego typu jest zdefiniowanie funkcji niebędącej szablonem funkcja:

#include <iostream>

template <typename T>
void print(const T& t)
{
    std::cout << t << '\n';
}

void print(double d)
{
    std::cout << std::scientific << d << '\n';
}

int main()
{
    print(5);
    print(6.7);
    
    return 0;
}

Kiedy kompilator przejdzie do rozwiązania print(6.7), zobaczy, że print(double) został już przez nas zdefiniowany i użyje tego zamiast tworzenia wersji z print(const T&).

Daje to wynik:

5
6.700000e+000

Jedną miłą rzeczą w definiowaniu funkcji w ten sposób jest to, że funkcja niebędąca szablonem nie musi mieć tego samego podpisu co szablon funkcji. Należy zauważyć, że print(const T&) używa przekazywania przez stałą referencję, podczas gdy print(double) używa przekazywania przez wartość.

Generalnie wolę definiować funkcję inną niż szablon, jeśli taka opcja jest dostępna.

Specjalizacja szablonu funkcji

Innym sposobem osiągnięcia podobnego rezultatu jest użycie jawnej specjalizacji szablonu. Jawna specjalizacja szablonu (często skracana do szablon specjalizacja) to funkcja, która pozwala nam jawnie zdefiniować różne implementacje szablonu dla określonych typów lub wartości. Kiedy wszystkie parametry szablonu są wyspecjalizowane, nazywa się to pełną specjalizacją. Gdy tylko niektóre parametry szablonu są wyspecjalizowane, nazywa się to specjalizacją częściową.

Stwórzmy specjalizację print<T> gdy T jest double:

#include <iostream>

// Here's our primary template (must come first)
template <typename T>
void print(const T& t)
{
    std::cout << t << '\n';
}

// A full specialization of primary template print<T> for type double
// Full specializations are not implicitly inline, so make this inline if put in header file
template<>                          // template parameter declaration containing no template parameters 
void print<double>(const double& d) // specialized for type double
{
    std::cout << std::scientific << d << '\n'; 
}

int main()
{
    print(5);
    print(6.7);
    
    return 0;
}

Aby wyspecjalizować szablon, kompilator musi najpierw zobaczyć deklarację szablonu podstawowego. Podstawowym szablonem w powyższym przykładzie jest print<T>(const T&).

Przyjrzyjmy się teraz bliżej naszej specjalizacji szablonów funkcji:

template<>                          // template parameter declaration containing no template parameters 
void print<double>(const double& d) // specialized for type double

Po pierwsze potrzebujemy deklaracji parametrów szablonu, aby kompilator wiedział, że robimy coś związanego z szablonami. Jednak w tym przypadku tak naprawdę nie potrzebujemy żadnych parametrów szablonu, więc używamy pustej pary nawiasów ostrych. Ponieważ w specjalizacji nie mamy parametrów szablonu, jest to specjalizacja pełna.

W następnym wierszu print<double> informuje kompilator, że specjalizujemy print podstawową funkcję szablonu dla typu double. Specjalizacja musi mieć ten sam podpis co szablon podstawowy (z wyjątkiem substytutów specjalizacji double wszędzie tam, gdzie używany jest szablon podstawowy T). Ponieważ szablon podstawowy ma parametr typu const T&, specjalizacja musi mieć parametr typu const double&. Specjalizacja nie może używać przekazywania przez wartość, gdy podstawowy szablon korzysta z przekazywania przez odwołanie (lub odwrotnie).

W tym przykładzie zostanie wyświetlony taki sam wynik jak powyżej.

Zauważ, że jeśli istnieją pasujące funkcje nieszablonowy i pasujące specjalizacje funkcji szablonu, funkcja nieszablonowa będzie miała pierwszeństwo. Ponadto pełne specjalizacje nie są domyślnie wbudowane, więc jeśli zdefiniujesz je w pliku nagłówkowym, upewnij się, inline że to robisz, aby uniknąć naruszeń ODR.

Ostrzeżenie

Pełne specjalizacje nie są domyślnie wbudowane (specjalizacje częściowe są domyślnie wbudowane). Jeżeli w pliku nagłówkowym umieścisz pełną specjalizację, należy ją oznaczyć jako inline , aby nie powodowała naruszeń ODR przy włączeniu do wielu jednostek tłumaczeniowych.

Podobnie jak normalne funkcje, specjalizacje szablonów funkcji można usunąć (za pomocą = delete), jeśli chcesz, aby wywołania funkcji, które odnoszą się do specjalizacji, powodowały błąd kompilacji.

Ogólnie rzecz biorąc, jeśli to możliwe, powinieneś unikać specjalizacji szablonów funkcji na rzecz funkcji innych niż szablony.

Specjalizacja szablonów funkcji dla funkcji składowych?

Rozważmy teraz następujący szablon klasy:

#include <iostream>

template <typename T>
class Storage
{
private:
    T m_value {};
public:
    Storage(T value)
      : m_value { value }
    {
    }

    void print()
    {
        std::cout << m_value << '\n';
    }
};

int main()
{
    // Define some storage units
    Storage i { 5 };
    Storage d { 6.7 };

    // Print out some values
    i.print();
    d.print();
}

Wypisuje:

5
6.7

Załóżmy, że ponownie chcemy utworzyć wersję funkcja print() wypisująca liczbę podwójną w notacji naukowej. Jednak tym razem print() jest to funkcja składowa, więc nie możemy zdefiniować funkcji niebędącej składową. Jak więc moglibyśmy to zrobić?

Chociaż może się wydawać, że musimy tutaj zastosować specjalizację szablonów funkcji, jest to niewłaściwe narzędzie. Zauważ, że i.print() wywołania Storage<int>::print() i d.print() wywołania Storage<double>::print(). Dlatego jeśli chcemy zmienić zachowanie tej funkcji, gdy T jest wartością podwójną, musimy wyspecjalizować Storage<double>::print(), która jest specjalizacją w szablonie klasy, a nie w szablonie funkcji!

Jak więc możemy to zrobić? Specjalizację szablonów zajęć omówimy na następnej lekcji.

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
132 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze