Częściowa specjalizacja szablonów - Partial template specialization

Częściowa specjalizacja szablonów to szczególna forma specjalizacji szablonów klas . Zwykle używany w odniesieniu do języka programowania C++ , pozwala programiście na specjalizację tylko niektórych argumentów szablonu klasy, w przeciwieństwie do jawnej pełnej specjalizacji, gdzie podane są wszystkie argumenty szablonu.

Szablony i specjalizacja

Szablony klas są w rzeczywistości metaklasami: są to częściowe abstrakcyjne typy danych, które dostarczają kompilatorowi instrukcji tworzenia klas z odpowiednimi członkami danych. Na przykład standardowe kontenery C++ to szablony klas. Kiedy programista używa wektora, tworzy go z określonym typem danych, na przykład int, string lub double. Każdy typ wektora skutkuje inną klasą w kodzie obiektowym kompilatora, z których każda działa z innym typem danych. Ten proces nazywa się monomorfizacją generyków.

Jeśli wiadomo, że szablon klasy będzie dość często używany z określonym typem danych i ten typ danych pozwala na pewne optymalizacje (np. przesuwanie bitów za pomocą liczb całkowitych, w przeciwieństwie do mnożenia lub dzielenia przez 2), można wprowadzić wyspecjalizowany szablon klasy z pewnymi wstępnie ustawionych parametrów szablonu. Kiedy kompilator zobaczy taki szablon klasy utworzony w kodzie, zwykle wybierze najbardziej wyspecjalizowaną definicję szablonu, która pasuje do wystąpienia. Dlatego jawna pełna specjalizacja (taka, w której określone są wszystkie argumenty szablonu) będzie preferowana od częściowej specjalizacji, jeśli wszystkie argumenty szablonu są zgodne.

Częściowa specjalizacja

Szablony mogą mieć więcej niż jeden typ parametru. Niektóre starsze kompilatory pozwalają tylko na specjalizację wszystkich lub żadnego z parametrów szablonu. Kompilatory obsługujące częściową specjalizację pozwalają programiście na specjalizację niektórych parametrów, pozostawiając inne ogólne.

Przykład

Załóżmy, że istnieje KeyValuePairklasa z dwoma parametrami szablonu, jak poniżej.

template <typename Key, typename Value>
class KeyValuePair {};

Poniżej znajduje się przykład klasy, która definiuje jawną pełną specjalizację szablonu KeyValuePairprzez parowanie liczb całkowitych z ciągami. Typ klasy zachowuje tę samą nazwę, co wersja oryginalna.

template <>
class KeyValuePair<int, std::string> {};

Następny to przykład częściowej specjalizacji o KeyValuePairtej samej nazwie co wersja oryginalna i jednym wyspecjalizowanym parametrze szablonu.

template <typename Key>
class KeyValuePair<Key, std::string> {};

Kolejny przykład klasa KeyStringPairjest pochodzący z oryginału KeyValuePairz nową nazwą i definiuje częściową specjalizację szablonu. W przeciwieństwie do wyraźnej specjalizacji powyżej, tylko wartość parametru szablonu z nadklasy specjalizuje, a Key parametr szablonu pozostaje rodzajowy.

template <typename Key>
class KeyStringPair : public KeyValuePair<Key, std::string> {};

Nie ma znaczenia, które parametry szablonu są wyspecjalizowane, a które pozostają ogólne. Na przykład poniższy przykład jest również poprawnym przykładem częściowej specjalizacji oryginalnej KeyValuePairklasy.

template <typename Value>
class IntegerValuePair : public KeyValuePair<int, Value> {};

Zastrzeżenia

Szablony C++ nie ograniczają się do klas — mogą być również używane do definiowania szablonów funkcji . Chociaż szablony funkcji mogą być w pełni wyspecjalizowane, nie mogą być częściowo wyspecjalizowane, niezależnie od tego, czy są szablonami funkcji członkowskich, czy szablonami funkcji niebędących członkami. Może to być korzystne dla twórców kompilatorów, ale wpływa na elastyczność i szczegółowość tego, co mogą zrobić programiści. Ale szablony funkcji mogą być przeciążone , co daje prawie taki sam efekt, jak w przypadku częściowej specjalizacji szablonu funkcji. Poniższe przykłady służą zilustrowaniu tych punktów.

// legal: base function template
template <typename ReturnType, typename ArgumentType>
ReturnType Foo(ArgumentType arg);

// legal: explicit/full function template specialization
template <>
std::string Foo<std::string, char>(char arg) { return "Full"; }

// illegal: partial function template specialization of the return type
//          function template partial specialization is not allowed
// template <typename ArgumentType>
// void Foo<void, ArgumentType>(ArgumentType arg);

// legal: overloads the base template for a pointer argument type
template <typename ReturnType, typename ArgumentType>
ReturnType Foo(ArgumentType *argPtr) { return "PtrOverload"; }

// legal: base function name reused. Not considered an overload. ill-formed: non-overloadable declaration (see below)
template <typename ArgumentType>
std::string Foo(ArgumentType arg) { return "Return1"; }

// legal: base function name reused. Not considered an overload. ill-formed: non-overloadable declaration (see below)
template <typename ReturnType>
ReturnType Foo(char arg) { return "Return2"; }

W powyższym przykładzie zauważ, że chociaż dwie ostatnie definicje funkcji Foosą zgodne z C++, są one uważane za źle sformułowane zgodnie ze standardem, ponieważ są to deklaracje, których nie można przeciążać. Dzieje się tak, ponieważ definicja przeciążania funkcji uwzględnia tylko nazwę funkcji, listę typów parametrów i otaczającą przestrzeń nazw (jeśli istnieje). Nie uwzględnia typu zwrotu. Jednak te funkcje można nadal wywoływać, jawnie wskazując podpis kompilatorowi, jak pokazano w poniższym programie.

// note: to be compiled in conjunction with the definitions of Foo above

int main(int argc, char *argv[])
{
    char c = 'c';
    std::string r0, r1, r2, r3;
    // let the compiler resolve the call
    r0 = Foo(c);
    // explicitly specify which function to call
    r1 = Foo<std::string>(c);
    r2 = Foo<std::string, char>(c);
    r3 = Foo<std::string, char>(&c);
    // generate output
    std::cout << r0 << " " << r1 << " " << r2 << " " << r3 << std::endl;
    return 0;
}

//expected output:
Return1 Return2 Full PtrOverload

Bibliografia