Widziałem kilka przykładów C ++ używających parametrów szablonów szablonów (czyli szablonów, które biorą szablony jako parametry) do tworzenia klas opartych na zasadach. Jakie inne zastosowania ma ta technika?
c++
templates
template-templates
Ferruccio
źródło
źródło
Odpowiedzi:
Myślę, że musisz użyć składni szablonu szablonu, aby przekazać parametr, którego typ jest szablonem zależnym od innego szablonu, takiego jak ten:
Tutaj
H
jest szablon, ale chciałem, aby ta funkcja obsługiwała wszystkie specjalizacjeH
.UWAGA : Programuję c ++ od wielu lat i potrzebowałem tego tylko raz. Uważam, że jest to rzadko potrzebna funkcja (oczywiście przydatna, gdy jej potrzebujesz!).
Próbowałem wymyślić dobre przykłady i szczerze mówiąc, przez większość czasu nie jest to konieczne, ale wymyślmy przykład. Udawajmy, że
std::vector
nie matypedef value_type
.Jak więc napisać funkcję, która może tworzyć zmienne odpowiedniego typu dla elementów wektorów? To by działało.
UWAGA :
std::vector
ma dwa parametry szablonu, typ i alokator, więc musieliśmy zaakceptować oba z nich. Na szczęście z powodu dedukcji typu nie będziemy musieli jawnie wypisywać dokładnego typu.którego możesz użyć w następujący sposób:
lub jeszcze lepiej, możemy po prostu użyć:
AKTUALIZACJA : Nawet ten wymyślony przykład, choć ilustracyjny, nie jest już niesamowitym przykładem ze względu na wprowadzenie c ++ 11
auto
. Teraz tę samą funkcję można zapisać jako:tak wolałbym pisać tego typu kod.
źródło
template<template<class, class> class C, class T, class U> void f(C<T, U> &v)
f<vector,int>
i nief<vector<int>>
.f<vector,int>
oznaczaf<ATemplate,AType>
,f<vector<int>>
znaczyf<AType>
W rzeczywistości przypadek użycia parametrów szablonu szablonu jest dość oczywisty. Gdy dowiesz się, że C ++ stdlib ma otwartą lukę w nieokreślaniu operatorów wyjściowych strumienia dla standardowych typów kontenerów, możesz napisać coś takiego:
Wtedy odkryłbyś, że kod dla wektora jest taki sam, dla forward_list jest w rzeczywistości taki sam, nawet dla wielu typów map jest nadal taki sam. Te klasy szablonów nie mają ze sobą nic wspólnego oprócz meta-interfejsu / protokołu, a użycie parametru szablonu szablonu pozwala uchwycić podobieństwo we wszystkich z nich. Przed przystąpieniem do pisania szablonu warto jednak sprawdzić odniesienie, aby przypomnieć, że kontenery sekwencji akceptują 2 argumenty szablonu - dla typu wartości i alokatora. Chociaż domyślnie jest przydzielany alokator, nadal powinniśmy uwzględnić jego istnienie w naszym operatorze szablonów <<:
Voila, która będzie działać automatycznie dla wszystkich obecnych i przyszłych kontenerów sekwencji zgodnych ze standardowym protokołem. Aby dodać mapy do miksu, należy zerknąć na odniesienie, aby zauważyć, że akceptują 4 parametry szablonu, więc potrzebowalibyśmy innej wersji operatora << z param szablonem 4-arg. Zobaczymy również, że std: pair próbuje być renderowany za pomocą operatora 2-arg << dla typów sekwencji, które wcześniej zdefiniowaliśmy, więc zapewnilibyśmy specjalizację tylko dla std :: pair.
Btw, z C + 11, który pozwala na szablony variadic (a zatem powinien pozwalać na argumenty szablonów variadic szablonów), możliwe byłoby posiadanie jednego operatora <<, aby rządzić nimi wszystkimi. Na przykład:
Wynik
źródło
__PRETTY_FUNCTION__
, które między innymi zgłasza opisy parametrów szablonu w postaci zwykłego tekstu. clang też to robi. Czasami bardzo przydatna funkcja (jak widać).Oto prosty przykład zaczerpnięty z „Modern C ++ Design - Generic Programming and Design Patterns Applied” autorstwa Andrei Alexandrescu:
Używa klas z parametrami szablonu szablonu w celu wdrożenia wzorca zasad:
Wyjaśnia: Zazwyczaj klasa hosta już zna lub może łatwo wywnioskować argument szablonu klasy strategii. W powyższym przykładzie WidgetManager zawsze zarządza obiektami typu Widget, więc wymaganie od użytkownika ponownego podania Widget w momencie tworzenia CreationPolicy jest zbędne i potencjalnie niebezpieczne. W takim przypadku kod biblioteki może wykorzystywać parametry szablonu szablonu do określania zasad.
W rezultacie kod klienta może używać „WidgetManager” w bardziej elegancki sposób:
Zamiast bardziej nieporęcznego i podatnego na błędy sposobu, który wymagałaby definicja pozbawiona argumentów szablonu szablonu:
źródło
Oto kolejny praktyczny przykład z mojej biblioteki sieci neuronowej CUDA Convolutional . Mam następujący szablon klasy:
który faktycznie implementuje manipulację macierzami n-wymiarowymi. Istnieje również szablon klasy podrzędnej:
który implementuje tę samą funkcjonalność, ale w GPU. Oba szablony mogą współpracować ze wszystkimi podstawowymi typami, takimi jak float, double, int itp. Mam też szablon klasy (uproszczony):
Powodem tego jest składnia szablonu szablonu, ponieważ mogę zadeklarować implementację klasy
które będą miały zarówno wagi, jak i dane wejściowe typu float i na GPU, ale macierz połączeń będzie zawsze int, albo na CPU (poprzez określenie TT = Tensor), albo na GPU (przez określenie TT = TensorGPU).
źródło
Załóżmy, że używasz CRTP w celu zapewnienia „interfejsu” dla zestawu szablonów potomnych; a zarówno element nadrzędny, jak i podrzędny są parametryczne w innych argumentach szablonu:
Zwróć uwagę na powielenie „int”, które w rzeczywistości jest parametrem tego samego typu określonym dla obu szablonów. Możesz użyć szablonu dla DERIVED, aby uniknąć tego powielania:
Zauważ, że eliminujesz bezpośrednio dostarczając inne parametry szablonu do szablonu pochodnego ; „interfejs” nadal je odbiera.
Pozwala to również budować typedefs w „interfejsie”, które zależą od parametrów typu, które będą dostępne z szablonu pochodnego.
Powyższy typedef nie działa, ponieważ nie można wpisać do nieokreślonego szablonu. Działa to jednak (a C ++ 11 ma natywną obsługę szablonów typedefs):
Niestety potrzebujesz jednego typu pochodnej_interface dla każdej instancji szablonu pochodnego, chyba że jest jeszcze jedna sztuczka, której jeszcze się nie nauczyłem.
źródło
derived
można używać klasy szablonów bez argumentów szablonu, tj. Liniitypedef typename interface<derived, VALUE> type;
template <typename>
. W pewnym sensie możesz myśleć o parametrach szablonu jako o „metatype”; normalnym typem metate dla parametru szablonu jesttypename
to, że musi być wypełniony typem regularnym; tetemplate
środki metatype musi być wypełniona odniesienia do szablonu.derived
definiuje szablon, który akceptuje jedentypename
parametr metatowany, więc pasuje do rachunku i można się do niego odwoływać tutaj. Ma sens?typedef
. Możesz także uniknąć duplikatuint
w pierwszym przykładzie, używając standardowej konstrukcji, takiej jakvalue_type
typu DERIVED.typedef
problem z bloku 2. Ale punkt 2 jest poprawny, myślę ... tak, prawdopodobnie byłby to prostszy sposób na zrobienie tego samego.Oto na co wpadłem:
Można rozwiązać:
lub (kod roboczy):
źródło
W rozwiązaniu z szablonami variadic dostarczonymi przez pfalcon trudno było mi faktycznie specjalizować operatora ostream dla std :: map ze względu na chciwość natury specjalizacji variadic. Oto niewielka zmiana, która zadziałała dla mnie:
źródło
Oto jeden uogólniony z czegoś, czego właśnie użyłem. Publikuję go, ponieważ jest to bardzo prosty przykład i pokazuje praktyczny przypadek użycia wraz z domyślnymi argumentami:
źródło
Poprawia to czytelność kodu, zapewnia dodatkowe bezpieczeństwo typów i oszczędza trochę wysiłku kompilatora.
Powiedzmy, że chcesz wydrukować każdy element kontenera, możesz użyć następującego kodu bez parametru szablonu szablonu
lub z parametrem szablonu szablonu
Załóżmy, że przekazujesz liczbę całkowitą
print_container(3)
. W pierwszym przypadku szablon zostanie utworzony przez kompilator, który narzeka na użyciec
w pętli for, ten drugi w ogóle nie utworzy szablonu, ponieważ nie można znaleźć pasującego typu.Ogólnie rzecz biorąc, jeśli klasa / funkcja szablonu została zaprojektowana do obsługi klasy szablonu jako parametru szablonu, lepiej jest to wyjaśnić.
źródło
Używam go dla typów wersjonowanych.
Jeśli masz typ wersjonowany za pomocą szablonu, takiego jak
MyType<version>
, możesz napisać funkcję, w której możesz przechwycić numer wersji:Możesz więc robić różne rzeczy w zależności od przekazywanej wersji typu, zamiast przeciążania każdego typu. Możesz również mieć funkcje konwersji, które przyjmują
MyType<Version>
i zwracająMyType<Version+1>
, w ogólny sposób, a nawet ponownie je skonfigurować, aby miałyToNewest()
funkcję, która zwraca najnowszą wersję typu z dowolnej starszej wersji (bardzo przydatne dla dzienników, które mogły być przechowywane jakiś czas temu ale muszą zostać przetworzone za pomocą najnowszego narzędzia).źródło