Większość implementacji generycznych (a raczej: parametrycznego polimorfizmu) używa usuwania typu. To znacznie upraszcza problem kompilowania kodu ogólnego, ale działa tylko dla typów pudełkowych: ponieważ każdy argument jest faktycznie nieprzezroczystym wskaźnikiem, potrzebujemy VTable lub podobnego mechanizmu wysyłania, aby wykonywać operacje na argumentach. W Javie:
<T extends Addable> T add(T a, T b) { … }
można skompilować, sprawdzić typ i wywołać w taki sam sposób jak
Addable add(Addable a, Addable b) { … }
z wyjątkiem tego, że generycy dostarczają kontrolerowi typu znacznie więcej informacji na stronie połączenia. Ta dodatkowa informacja może być obsługiwana przez zmienne typu , szczególnie gdy wnioskuje się o typach ogólnych. Podczas sprawdzania typu każdy typ ogólny można zastąpić zmienną, nazwijmy to $T1
:
$T1 add($T1 a, $T1 b)
Zmienna typu jest następnie aktualizowana o więcej faktów, gdy stają się znane, aż można ją zastąpić konkretnym typem. Algorytm sprawdzania typu musi być napisany w sposób uwzględniający te zmienne typu, nawet jeśli nie są one jeszcze rozpoznane jako kompletny typ. W samej Javie można to zwykle zrobić łatwo, ponieważ typ argumentów jest często znany, zanim trzeba będzie poznać typ wywołania funkcji. Godnym uwagi wyjątkiem jest wyrażenie lambda jako argument funkcji, które wymaga użycia takich zmiennych typu.
Znacznie później optymalizator może wygenerować wyspecjalizowany kod dla określonego zestawu argumentów, co byłoby efektywnym rodzajem wstawiania.
Tabeli VT dla argumentów typu ogólnego można uniknąć, jeśli funkcja ogólna nie wykonuje żadnych operacji na typie, a jedynie przekazuje je do innej funkcji. Np. Funkcja Haskell call :: (a -> b) -> a -> b; call f x = f x
nie musiałaby wstawiać x
argumentu. Wymaga to jednak konwencji wywoływania, która może przekazywać wartości bez znajomości ich rozmiaru, co zasadniczo ogranicza je do wskaźników.
Pod tym względem C ++ bardzo różni się od większości języków. Klasa lub funkcja szablonowa (tutaj omówię tylko funkcje szablonowe) sama w sobie nie jest możliwa do wywołania. Zamiast tego szablony należy rozumieć jako meta-funkcję w czasie kompilacji, która zwraca rzeczywistą funkcję. Ignorując przez chwilę wnioskowanie o szablonie, ogólne podejście sprowadza się do następujących kroków:
Zastosuj szablon do podanych argumentów szablonu. Np nazywając template<class T> T add(T a, T b) { … }
jak add<int>(1, 2)
dałoby nam rzeczywistą funkcję int __add__T_int(int a, int b)
(lub cokolwiek jest używana nazwa-maglowania podejście).
Jeśli kod dla tej funkcji został już wygenerowany w bieżącej jednostce kompilacji, kontynuuj. W przeciwnym razie wygeneruj kod tak, jakby funkcja int __add__T_int(int a, int b) { … }
została zapisana w kodzie źródłowym. Wymaga to zastąpienia wszystkich wystąpień argumentu szablonu jego wartościami. Prawdopodobnie jest to transformacja AST → AST. Następnie sprawdź typ wygenerowanego AST.
Skompiluj wywołanie, jakby był kod źródłowy __add__T_int(1, 2)
.
Zauważ, że szablony C ++ mają złożoną interakcję z mechanizmem rozwiązywania problemu przeciążenia, którego nie chcę tutaj opisywać. Zauważ również, że to generowanie kodu uniemożliwia stworzenie metody opartej na szablonie, która jest również wirtualna - podejście oparte na usuwaniu typów nie cierpi z powodu tego znacznego ograniczenia.
Co to oznacza dla twojego kompilatora i / lub języka? Musisz dokładnie przemyśleć rodzaj leków generycznych, które chcesz zaoferować. Usuwanie typu w przypadku braku wnioskowania o typach jest najprostszym możliwym podejściem, jeśli obsługiwane są typy pudełkowe. Specjalizacja szablonów wydaje się dość prosta, ale zwykle wiąże się ze zniekształcaniem nazw i (w przypadku wielu jednostek kompilacji) znacznego powielania danych wyjściowych, ponieważ szablony są tworzone w miejscu wywołania, a nie w miejscu definicji.
Podejście, które pokazałeś, jest zasadniczo oparte na C ++. Przechowujesz jednak szablony specjalistyczne / tworzone jako „wersje” szablonu głównego. Jest to mylące: nie są one takie same koncepcyjnie, a różne instancje funkcji mogą mieć bardzo różne typy. To skomplikuje sytuację na dłuższą metę, jeśli pozwolisz również na przeciążenie funkcji. Zamiast tego potrzebujesz pojęcia zestawu przeciążenia, który zawiera wszystkie możliwe funkcje i szablony o wspólnej nazwie. Oprócz rozwiązania problemu przeciążenia można rozważyć różne szablony, które są całkowicie od siebie oddzielone.