Пробно списание

Page 1

Генеричко програмирање Генеричкото програмирање претставува програмирање независно од типот на податоци над кои ќе оперира. Тоа значи дека истата програма ќе работи со било кој тип на податоци кои ќе и бидат проследени. 1. Различно е имплементирано во различни јазици 2. Се среќава во: Ada, BETA, C++, D, Eiffel, Java, C# Механизмот преку кој е реализирано генеричкото програмирање во C++ се шаблоните (темплејти, templates). Кај генеричките функции и класи типот на променливите со кои работат истите се специфицира како параметар или се одредуваат од самиот преведувач, зависно од нивната употреба. Ова значи дека функцијата или класата напишани како шаблон ќе може да се искористат за повеќе различни типови на податоци без потребата да се пишуваат различни верзии за секој тип на податоци. Постојат два вида шаблони: 1. Функциски темплејти (function templates) 2. Класни темплејти (class templates) Да ја претпоставиме следнава ситуација. Имаме функција која пресметува сума на елементи на поле од цели броеви. int suma(int *pole, unsigned n) { int zbir = 0; for (int idx = 0; idx < n; ++idx) zbir += pole[idx]; return (zbir); }

Функцијата треба да се преоптеретува ако имаме поле на децимални броеви double suma(double *pole, unsigned n) { double zbir = 0; for (int idx = 0; idx < n; ++idx) zbir += pole[idx]; return (zbir); }

Во ваквите случаи се користат параметризираните функции за да се добие вистински општа функција. Параметризираните функции можат да се сметаат како некој рецепт или алгоритам за креирање на функција која може да биде употребена со општи видови на податоци.


Дефиницијата на параметризираните функции е многу слична на дефиницијата на обичните функции со тоа што кај нив видовите на податоци што се проследуваат, користат и враќаат од функциите се параметризирани. Кај параметризираните функции и класи видот на податоци со кои се оперира се проследува како параметар. На овој начин се креира една функција или класа за повеќе видови на податоци. Со параметризираните функции алгоритмите се кодираат независно од податоците. Преведувачот е оној кој зависно од проследениот вид на податоци автоматски ја генерира потребната функција. На некој начин ова може да се нарече автоматско преоптоварување. Општата форма на овие функции е следнава template <class ime> vid ime_na funkcija( lista ) { // telo na funkcijata } Пример програма #include<iostream.h> template <class T> T suma(T *pole, int n) { T suma(0); for (int idx = 0; idx < n; ++idx) suma += pole[idx]; return (suma); } void main() { int x[] = {1, 2}; double y[] = {1.1, 2.2}; cout << suma(x, 2) << endl<<suma(y, 2) << endl; }

3 3.3

Клучните зборови template и class се употребуваат за дефинирање на параметризираните функции. Тие покрај параметризирани функции (generic function) се викаат и шаблонски функции (template function).


Функциски темплејти Функциските темплејти се функции кои се параметризирани така да претставуваат фамилија на функции. Овој вид на темплејти овозможуваат повикување на функциите за различни податочни типови. Репрезентацијата на функцијата е слична на обична функција, со таа разлика што некои елементи на функцијата се недефинирани. Овие елементи се всушност параметризирани. Дефинирање на темплејт: Функциски темплејт кој го враќа максимумот од два броеви. // basics/max.hpp template <typename T> inline T const& max (T const& a, T const& b) { // if a < b then use b else use a return a<b?b:a; }

Типот на параметрите е оставен отворен како параметар темплејт T. Темплејт параметрите мора да се наведат со следнава синтакса:

template < comma-separated-list-of-parameters > Клучниот збор typename означува таканаречен type parameter. Во нашиот случај type parameter е T. Типот на овој параметар се наведува при повикот на функцијата. Може да се користи било кој тип (основен податочен тип, класа итн.) кој ја обезбедува операцијата која ја користи темплејтот. Од историски причини може да се користи class наместо typename за да се дефинира type parameter. пример: template <class T> inline T const& max (T const& a, T const& b) { // if a < b then use b else use a return a<b?b:a; }

Употреба на темплејти Употреба на max()функцискиот темплејт: // basics/max.cpp #include <iostream> #include <string> #include "max.hpp" int main() { int i = 42;


}

std::cout << "max(7,i): " << ::max(7,i) << std::endl; double f1 = 3.4; double f2 = -6.7; std::cout << "max(f1,f2): " << ::max(f1,f2) << std::endl; std::string s1 = "mathematics"; std::string s2 = "math"; std::cout << "max(s1,s2): " << ::max(s1,s2) << std::endl;

програмата го дава следниов излез: max(7,i): 42 max(f1,f2): 3.4 max(s1,s2): mathematics

Употребата на :: овозможува повик до функцискиот темплејт кој се наоѓа во глобалниот простор на имиња, а не темплејтот од стандардната библиотека. Темплејтите не се компајлираат во единствен ентитет кој може да се справи со било кој податочен тип, напротив различни ентитети се генерираат од темплејтот за секој од типовите за кој го користиме темплејтот (во нашиот пример max() се компајлира за секој податочен тип) Првиот повик на функцијата: int i = 42; … max(7,i) …

го користи функцискиот темплејт со int како template parameter T. inline int const& max (int const& a, int const& b) { // if a < b then use b else use a return a<b?b:a; }

Процесот на замена на параметрите со конкретни податочни типови се нарекува инстанцирање (instantiation). const double& max (double const&, double const&); const std::string& max (std::string const&, std::string const&);

обид да се инстанцира темплејт за тип кој не ја подржува операцијата која се користи резултира во грешка при компајлирање. Пример: std::complex<float> c1, c2; // doesn't provide operator < … max(c1,c2); // ERROR at compile time

Темплејтите се компајлираат двапати: 1. Без инстанцирање, темплејтите се проверуваат за коректна синтакса. 2. За време на инстанцирањето, кодот се проверува за да се осигураме дека сите повици се валидни. Автоматска конверзија не е дозволена:


template <typename T> inline T const& max (T const& a, T const& b); … max(4,7) // OK: T is int for both arguments max(4,4.2) // ERROR: first T is int, second T is double

Можни начини за справување со грешките: 1. кастирање на аргументите: max(static_cast<double>(4),4.2) // OK

2. одредување на типот на T: max<double>(4,4.2) // OK

3. наведување дека параметрите може да имаат различни типови.

Template параметри и call параметри Функциските темплејти имаат два вида на параметри: 1. Template parameters, декларирани пред името на функцијата: template <typename T> // T is template parameter

2. Call parameters,декларирани после името на функцијата: … max (T const& a, T const& b) // a and b are call parameters

Дефинирање на функција со различен тип на call параметри: template <typename T1, typename T2> inline T1 max (T1 const& a, T2 const& b) { return a < b ? b : a; } … max(4,4.2) // OK, but type of first argument defines return type

проблеми: 1. мора да се наведе повратниот тип 2. непожелно конвертирање од еден во друг повратен тип (пример: бараме максимум на 42 и 66.66 кој може да биде double 66.66 или int 66. 3. конвертирањето на вториот аргумент за да одговара на повратниот тип креира нов локален објект заради што неможеме да го вратиме резултатот како референца (T1 наместо T1 const&). Инстанцирање на темплејт за одреден податочен тип: template <typename T> inline T const& max (T const& a, T const& b); … max<double>(4,4.2) // instantiate T as double

употреба на трет аргумент за дефинирање на повратниот тип: template <typename T1, typename T2, typename RT> inline RT max (T1 const& a, T2 const& b);


пример: template <typename T1, typename T2, typename RT> inline RT max (T1 const& a, T2 const& b); … max<int,double,double>(4,4.2) // OK, but tedious

процес на изведување на типовите на аргументите: template <typename RT, typename T1, typename T2> inline RT max (T1 const& a, T2 const& b); … max<double>(4,4.2) // OK: return type is double

Преоптоварување на функциски темплејти Исто како обичните функции и функциските темплејти може да се преоптоваруваат. пример: // basics/max2.cpp // maximum of two int values inline int const& max (int const& a, int const& b) { return a<b?b:a; } // maximum of two values of any type template <typename T> inline T const& max (T const& a, T const& b) { return a<b?b:a; } // maximum of three values of any type template <typename T> inline T const& max (T const& a, T const& b, T const& c) { return max (max(a,b), c); } int main() { ::max(7, 42, 68); // calls the template for three arguments ::max(7.0, 42.0); // calls max<double> (by argument deduction) ::max('a', 'b'); // calls max<char> (by argument deduction) ::max(7, 42); // calls the nontemplate for two ints ::max<>(7, 42); // calls max<int> (by argument deduction) ::max<double>(7, 42); // calls max<double> (no argument deduction) ::max('a', 42.7); // calls the nontemplate for two ints }

преферирање на обичната функција: max(7, 42) // both int values match the nontemplate function perfectly


ако темплејтот обезбедува функција која подобро одговара на повикот се повикува функцискиот темплејт: max(7.0, 42.0) // calls the max<double> (by argument deduction) max('a', 'b'); // calls the max<char> (by argument deduction)

празна листа на аргументи за темплејтите ограничува повик само на темплејт функциите: max<>(7, 42) // calls max<int> (by argument deduction)

Бидејќи автоматската конверзија не е присутна во темплејтите последниот повик е всушност повик кон nontemplate функцијата ('a' и 42.7 се конвертираат во int): max('a', 42.7) // only the nontemplate function allows different argument types

Следнава програма не работи еднакво за генерирани видови на класи #include <iostream.h> template <class T> T max(T a, T b) { return a > b ? a : b ; } void main() { cout << "max(10, 15) = " cout << "max('k', 's') = cout << "max(10.1, 15.2) cout << "max(\"Branko\", endl ; }

<< max(10, 15) << endl; " << max('k', 's') << endl ; = " << max(10.1, 15.2) << endl ; \"Aco\") = " << max("Branko", "Aco") <<

max(10, 15) = 15 max('k', 's') = s max(10.1, 15.2) = 15.2 max("Branko", "Aco") = Aco

Зошто Специјализирање на темплејт функција Да се специјализира нова темплејт функција за да се корегира програмата

#include <iostream.h> #include <string.h> template <class T> T max(T a, T b) { return a > b ? a : b ;


} template <> char* max(char* a, char* b) { return strcmp(a, b) > 0 ? a : b ; } void main() { cout << "max(10, 15) = " << max(10, 15) << endl; cout << "max('k', 's') = " << max('k', 's') << endl ; cout << "max(10.1, 15.2) = " << max(10.1, 15.2) << endl ; cout << "max(\"Branko\", \"Aco\") = " << max("Branko", "Aco") << endl ; }

max(10, 15) = 15 max('k', 's') = s max(10.1, 15.2) = 15.2 max("Branko", "Aco") = Branko

Генерирање на параметризирана функција baraj која ќе шребарува поле од објекти и ќе го враќа индексот на најдениот објект или -1 ако не е најден. #include <iostream.h> #include <string.h> template <class X> int baraj(X objekt, X *lista, int golemina) { int i; for(i=0; i<golemina; i++) if(objekt == lista[i]) return i; return -1; } void main() { int a[] = {1, 2, 3, 4}; char *c = "Ova e test"; double d[] = {1.1, 2.2, 3.3}; cout << baraj(3, a, 4) ; cout << endl; cout << baraj('a', c, (int) strlen(c)) ; cout << endl; cout << baraj(0.0, d, 3) << endl; }

Generirawe parametrizirana funkcija za podreduvawe na pole. #include <iostream.h> template <class vid> void qsort( vid *ia, int dolu, int gore ) { if( dolu < gore ) { int d = dolu;


int go = gore + 1; vid element = ia[dolu]; for(;;) { while(ia[++d] < element); while(ia[--go] > element); if(d < go) smeni(ia, d, go); ะตlse break; } smeni(ia, dolu, go); qsort(ia, dolu, go - 1); qsort(ia, go + 1, gore);

} } template <class vid> void smeni(vid *ia, int i, int j) { vid privr = ia[i]; ia[i] = ia[j]; ia[j] = privr; } template <class vid> void prikazi(vid *ia, int golemina) { cout << "< "; for(int ix = 0; ix < golemina; ix++) cout << ia[ix] << " "; cout << " >\n"; } double da[] = {26.7, 5.7, 37.7, 1.7, 61.7, 11.7, 59.7, 15.7, 48.7, 19.7, 14.9}; int ia[] = {503, 87, 512, 61, 908, 170, 897, 275, 653, 426, 154, 509, 612,677, 765, 703}; void main() { int golemina = sizeof(da)/sizeof(double); cout << " Podreduvanje na double pole (golemina == " << golemina << ") \n"; qsort(da,0,golemina - 1); prikazi(da, golemina); golemina = sizeof(ia)/sizeof(int); cout << " Podreduvanje na int pole (golemina == " << golemina << ") \n"; qsort(ia, 0, golemina - 1); prikazi(ia, golemina); } Podreduvanje na double pole (golemina == 11) < 1.7 5.7 11.7 14.9 15.7 19.7 26.7 37.7 48.7 59.7 61.7 Podreduvanje na int pole (golemina == 16) < 61 87 154 170 275 426 503 509 512 612 653 677 703 897 908 >

> 765


Декларација на Class Templates template <typename T> class Stack { … }; или: template <class T> class Stack { … };

Dadena e slednava ednostavna klasa #include <iostream.h> class abc { char data; public: abc(char x) { data = x; cout << data << endl; }; }; void main() { abc a('c'); }

Da se pretvori vo {ablonska klasa i da se istestira za rabota i so celi i so decimalni broevi

#include <iostream.h> template <class T> class abc { T data; public: abc(T x) { data = x; cout << data << endl; }; }; void main() { abc<char> a('c'); abc<int> b(123); abc<float> c(123.45f); }


 Specijalizirawe na {ablonskata klasa Specijaliziranata {ablonska klasa se definira na sledniov na~in template <> class class_name <type>

Soodvetno treba da se definiraat i ostanatite funkcii ~lenovi.

Potreben e duri i poseben konstruktor iako toj e ist so onoj na nespecijaliziraniot {ablon bidejki specijaliziraniot {ablon ne go nasleduva osnovniot.

#include <iostream.h> template <class T> class par { T vrednost1, vrednost2; public: par (T prv, T vtor) { vrednost1=prv; vrednost2=vtor; } T modul () { return 9999; } }; template <> class par <int> { int vrednost1, vrednost2; public: par (int prv, int vtor) { vrednost1=prv; vrednost2=vtor; } int modul (); }; template <> int par<int>::modul() { return vrednost1%vrednost2; } void main () { par <int> celi (10,7); par <float> decimalni(100.0,75.0); par <char> znakovi('a', 'b'); cout << celi.modul() << '\n'; cout << decimalni.modul() << '\n'; cout << znakovi.modul() << '\n'; }


Литература Kon gradba na {ablonska klasa se prio|a koga klasata e mo`na za pove}e vidovi na podatoci. Da pretpostavme deka sakame da konstruirame klasa bvektor koja }e mo`e da raboti so hipoteti~niot vid na podatoci T. Potrebno e klasata da se izgradi so slednive ~lenovi: -

konstruktor koj bi kreiral objekti od klasata bvektor so dadena golemina destruktor preoptovaren operator [ ] za pristap do sakanite elementi

// Template verzija na klasa bazenVektor #include <iostream.h> template <class T> class bazenVektor { public: // construktori i destruktori bazenVektor(unsigned int element); virtual ~bazenVektor(); // operator [] T &operator [] (unsigned int index) const; // dolzina na bazenVektor unsigned int dolz() const; private: T * podatoci; // elementi na bazenVektor unsigned int golemina; // broj na elementi }; // //

implementacija na klasata bazenVektor Konstruktor

template <class T> bazenVektor<T>::bazenVektor(unsigned int element) : golemina(element) { podatoci = new T[golemina]; } template <class T> bazenVektor<T>::~bazenVektor() { delete [] podatoci; podatoci = 0; } template <class T> T & bazenVektor<T>::operator [] (unsigned int index)const { // operator []: vraka referenca na element na // bazenVektor return podatoci[index]; } template <class T> unsigned int bazenVektor<T>::dolz() const {


// Vraka broj na elementi vo bazenVektor return golemina;

} // Upotreba na template klasa i template funkcija template <class T> void pecati (bazenVektor<T> & a) { // pecati elementi na bazenVektor for (unsigned int i = 0; i < a.dolz(); i++) cout << a[i] << " "; cout << endl; } void main() { // Deklaracija na primeroci od bazenVektor bazenVektor<int> A(15); bazenVektor<float> B(5); bazenVektor<char> C(8); unsigned int i; // indeks za jamka // Operacii. cout<<"dolz na A (15) e "<<A.dolz()<<endl; cout<<"dolz na B (5) e "<<B.dolz()<<endl; for (i=0; i < A.dolz(); i++) A[i] = 2*i; cout << "podatoci vo A (0, 2, 4,...) se "; pecati(A); float x; for (i=0, x=2.5; i<B.dolz(); i++, x+=3.0) B[i] = x; cout << "podatoci vo B (2.5, 5.5,...)se "; pecati(B); char c = 'f'; for (i=0; i < C.dolz(); i++, c++) C[i] = c; cout << "podatoci vo C ('f', 'g',...)se "; pecati(C); } dolz na A (15) e 15 dolz na B (5) e 5 podatoci vo A (0, 2, 4, ...) se 0 2 4 6 8 10 12 14 16 22 24 26 28 podatoci vo B (2.5, 5.5, ...) se 2.5 5.5 8.5 11.5 14.5 podatoci vo C ('f', 'g', ...) se f g h i j k l m

18

20

Od ovaa klasa da se sostavi klasa iBazenVektor za klasa {to }e raboti samo so celi broevi

// verzija na klasa iBazenVektor za celi broevi #include <iostream.h> class iBazenVektor { public: // construktori i destruktori iBazenVektor(unsigned int element); virtual ~iBazenVektor();


// operator [] int &operator [] (unsigned int index) const; // dolzina na iBazenVektor unsigned int dolz() const; private: int * podatoci; // elementi na iBazenVektor unsigned int golemina; // broj na elementi }; // implementacija na klasata iBazenVektor // Konstruktor iBazenVektor::iBazenVektor(unsigned int element) : golemina(element) { podatoci = new int[golemina]; } iBazenVektor::~iBazenVektor() { delete [] podatoci; podatoci = 0; } int & iBazenVektor::operator [] (unsigned int index)const { // operator []: vraka referenca na element na // iBazenVektor return podatoci[index]; } unsigned int iBazenVektor::dolz() const { // Vraka broj na elementi vo iBazenVektor return golemina; } void pecati (iBazenVektor & a) { // pecati elementi na iBazenVektor for (unsigned int i = 0; i < a.dolz(); i++) cout << a[i] << " "; cout << endl; } void main() { // Deklaracija na primeroci od iBazenVektor iBazenVektor A(15); unsigned int i; // indeks za jamka // Operacii. cout<<"dolz na A (15) e "<<A.dolz()<<endl; for (i=0; i < A.dolz(); i++) A[i] = 2*i; cout << "podatoci vo A (0, 2, 4,...) se "; pecati(A); } dolz na A (15) e 15 podatoci vo A (0, 2, 4, ...) se 0 22 24 26 28

2

4

6

8

10

12

14

16

Sli~no mo`e da se realizira i parametrizirana klasa za stekovi

18

20


// Ovaa programa demonstrira upotreba na parametriziran stack. #include <iostream.h> #define GOLEMINA 10 // Kreiraj klasa parametriziran stack template <class Stack_vid> class stack { Stack_vid stck[GOLEMINA]; // cuva stack int tos; // index za vrvot(top-of-stack) public: void init() { tos = 0; } // inicijalizacija na stack void push(Stack_vid ch); // push objekt na stack Stack_vid pop(); // pop objekt od stack }; // Push objekt. template <class Stack_vid> void stack<Stack_vid>::push(Stack_vid ob) { if(tos==GOLEMINA) { cout << "Stack e poln.\n"; return; } stck[tos] = ob; tos++; } // Pop objekt. template <class Stack_vid> Stack_vid stack<Stack_vid>::pop() { if(tos==0) { cout << "Stack e prazen.\n"; return 0; // vrati nula za prazen stack } tos--; return stck[tos]; } void main() { // Demonstrira stack od znakovi stack<char> s1, s2; // kreira dva stack-a int i; // inicijalizacija na stack - ovite s1.init(); s2.init(); s1.push('a'); s2.push('x'); s1.push('b'); s2.push('y'); s1.push('c'); s2.push('z'); for(i=0; i<3; i++) cout << "Pop s1: " << s1.pop() << "\n"; for(i=0; i<3; i++) cout << "Pop s2: " << s2.pop() << "\n"; // demonstriranje na dvoen stack stack<double> ds1, ds2; // kreiraj dva stack - a // inicijaliziraj gi stack - ovite ds1.init(); ds2.init(); ds1.push(1.1); ds2.push(2.2);


}

ds1.push(3.3); ds2.push(4.4); ds1.push(5.5); ds2.push(6.6); for(i=0; i<3; i++) cout << "Pop ds1: " << ds1.pop() << "\n"; for(i=0; i<3; i++) cout << "Pop ds2: " << ds2.pop() << "\n";

Izlezot od ovaa programa e Pop Pop Pop Pop Pop Pop Pop Pop Pop Pop Pop Pop

s1: s1: s1: s2: s2: s2: ds1: ds1: ds1: ds2: ds2: ds2:

c b a z y x 5.5 3.3 1.1 6.6 4.4 2.2

Пример за параметризирана класа за ред // Creiranje na parametrizirana klasa za red. #include <iostream.h> #define GOLEMINA 100 template <class RED> class red { RED pred[GOLEMINA]; // pole na redot int glava, opaska; public: red() { glava = opaska = 0; } void q(RED num); // stavi RED deq(); // zemi }; // Stavi vrednost template <class RED> void red<RED>::q(RED num) { if(opaska+1==glava || (opaska+1==GOLEMINA && !glava)) { cout << "Redot e poln.\n"; return; } opaska++; if(opaska==GOLEMINA) opaska = 0; pred[opaska] = num; } // zemi template <class RED> RED red<RED>::deq() { if(glava == opaska) { cout << "Redot e prazen.\n"; return 0;


} glava++; if(glava==GOLEMINA) glava = 0; return pred[glava];

} void main() { red<int> q1; red<char> q2; int i; for(i=1; i<=10; i++) { q1.q(i); q2.q(i-1+'A'); } for(i=1; i<=10; i++) { cout << "Zemi 1: " << q1.deq() << "\n"; cout << "Zemi 2: " << q2.deq() << "\n"; } }

Програмата ќе испише Zemi Zemi Zemi Zemi Zemi Zemi Zemi Zemi Zemi Zemi Zemi Zemi Zemi Zemi Zemi Zemi Zemi Zemi Zemi Zemi

1: 2: 1: 2: 1: 2: 1: 2: 1: 2: 1: 2: 1: 2: 1: 2: 1: 2: 1: 2:

1 A 2 B 3 C 4 D 5 E 6 F 7 G 8 H 9 I 10 J

[abloni i stati~ki podato~ni ~lenovi Sekoja klasa ili funkcija generirana od {ablon ima sopstveni kopii od stati~ni ~lenovi. Vo slednava primer programa template <class T>


class X { public: static T s; }; void main() { X<int> xi; X<char*> xc; } X<int> ima stati~en ~len od vidot int i X<char*> ima stati~en ~len od vidot char*. Kako se definiraat stati~nite ~lenovi kaj {ablonskite klasi }e bide objasneto so slednava primer programa

#include <iostream.h> template <class T> class X { public: static T s; }; template <class T> T X<T>::s = 0; template <> int X<int>::s = 3; template <> char* X<char*>::s = "Programiraj"; void main() { X<int> xi; cout << "xi.s = " << xi.s << endl; X<char*> xc; cout << "xc.s = " << xc.s << endl; } xi.s = 3 xc.s = Programiraj

Drug primer

#include <iostream.h> template <class T> void f(T t) { static T s = 0;


s = t; cout << "s = " << s << endl;

} void main() { f(10); f("Nemoj da programiras"); } s = 10 s = Nemoj da programiras

[abloni i nasledstvo #include <iostream.h> template <class T> class signal { protected: T frekvencija; public: signal(T x) {frekvencija = x; cout << "Klasa osnovna " << x << endl;} }; template <class T> class FM : public signal <T> { public: FM(T x) : signal<T>(x) {cout<<"Klasa nasledena"<<frekvencija<<endl;} }; void main() { FM<int> radio(94); }

Klasa osnovna 94 Klasa nasledena 94


Парцијална специјализација template <typename T1, typename T2> class MyClass { … }; // partial specialization: both template parameters have same type template <typename T> class MyClass<T,T> { … }; // partial specialization: second type is int template <typename T> class MyClass<T,int> { … }; // partial specialization: both template parameters are pointer types template <typename T1, typename T2> class MyClass<T1*,T2*> { … }; MyClass<int,float> mif; // uses MyClass<T1,T2> MyClass<float,float> mff; // uses MyClass<T,T> MyClass<float,int> mfi; // uses MyClass<T,int> MyClass<int*,float*> mp; // uses MyClass<T1*,T2*>


Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.