> Trailing Return Type: Jenerik programlama(fonksiyon şablonlarında) tarafında kullanılan bir yöntemdir. Modern Cpp ile gelen bir araçtır. Kullanımı aşağıdaki örnekteki gibidir. Normal fornksiyonların imzalarında kullanılması pek tavsiye edilmez. İstisnai durum olarak ikinci örneği inceleyiniz. * Örnek 1, // Some codes here... // Fonksiyonun geri dönüş değeri yerine 'auto' yazıyoruz. Sonrasında fonksiyon parametre parantezinden sonra bir // adet '->' tokeni ve fonksiyonun geri dönüş değerinin türünü yazıyoruz. auto func()->int { // codes... return 1; } * Örnek 2, // Some codes here... #include #include // 'strcmp()' gibi bir fonksiyon göstericisi döndüren fonksiyonların yazımı: int(*func1())(const char*, const char*) { return &strcmp; } // (1) İlk önce gösterici döndürecek olan fonksiyonun ismi ve varsa alacağı parametreler için parantezleri // yazılır => ...func()... // (2) Tıpkı diğer 'function pointer' larda olduğu gibi isimden önce bir adet '*' tokeni konulur => ...*func()... // (3) Tıpkı diğer 'function pointer' larda olduğu gibi deyimimiz dıştan paranteze alınır => ...(*func())... // (3) Sonrasında da en dıştaki parantez çiftinin soluna, 'function pointer' tarafından gösterilen fonksiyonun geri // dönüş türü yazılır => int(*func())... // (4) Son olarak da en dıştaki parantez çiftinin sağına, 'function pointer' tarafından gösterilen fonksiyonun //parametreleri yazılır => int(*func())(const char*, const char*) // AYNI 'func()' İSİMLİ FONKSİYONU 'Trailing Return Type' İLE YAZALIM: auto func2()->int(*)(const char*, const char*) { return &strcmp; } // AYNI 'func()' İSİMLİ FONKSİYONU 'typedef' BİLDİRİMİ İLE YAZALIM: typedef int(*myFuncPtr)(const char*, const char*); myFuncPtr func3() { return &strcmp; } // Function Pointer argüman olarak alan ve geri döndüren fonksiyon: myFuncPtr func4(myFuncPtr other) { return other; } // Function Pointer argüman olarak alan ve geri döndüren fonksiyon: // A B C A int ( func5 ( int(myFuncPtr)(const char, const char)) ) (const char, const char) { return myFuncPtr; } int ( func6 ( /* ...*/ ) (const char, const char*) { return &strcmp; } /* # AÇIKLAMALAR # A : Bahsi geçen esas fonksiyonumuzun döndürdüğü bir 'function-pointer' a ait bilgiler. Bu iş bu fonksiyonumuz, geri dönüş değeri 'int' olan ve 'const char*' türünden iki adet parametresi olan bir fonksiyon adresi döndürmektedir. B : Bahsi geçen esas fonksiyonumuzun ismi. İş bu fonksiyonu çağırmak için bu ismi kullanacağız. C : Bahsi geçen esas fonksiyonumuzun parametre bilgilerinin geçildiği yer. İş bu fonksiyona geçeceğimiz 'function-pointer', 'int' türden değer döndüren ve 'const char*' türünden iki adet parametre alan bir fonksiyonun adresi olmalıdır. Dolayısıyla bizim esas fonksiyonumuza geçilen argümanın ismi de 'myFuncPtr' olacaktır. */ int main() { const char* x = "Ahmet"; const char* y = "Merve"; std::cout << "strcmp(x,y) : " << strcmp(x,y) << "\n"; std::cout << "func1(x,y) : " << func1()(x,y) << "\n"; std::cout << "func2(x,y) : " << func2()(x,y) << "\n"; std::cout << "func3(x,y) : " << func3()(x,y) << "\n"; std::cout << "func4(strcmp) : " << func4(strcmp)(x,y) << "\n"; // 'typedef' kullanılırsa std::cout << "func5(strcmp) : " << func5(strcmp)(x,y) << "\n"; // 'typedef' kullanılmazsa } * Örnek 3, #include typedef int (*myFuncPTR)(int, int); int foo(int a, int b) { return a*b; } int func(myFuncPTR fp, int x, int y) { return fp(x,y); } myFuncPTR myFoo() { return foo; } int (*myFunc())(int a, int b); int main() { /* # OUTPUT # foo(1,2) : 2 func(foo, 2, 3) : 6 myFoo() : 12 myFunc() : 20 */ std::cout << "foo(1,2) : " << foo(1,2) << "\n"; std::cout << "func(foo, 2, 3) : " << func(foo, 2, 3) << "\n"; std::cout << "myFoo() : " << myFoo()(3, 4) << "\n"; std::cout << "myFunc() : " << myFunc()(4, 5) << "\n"; } int (*myFunc())(int a, int b) { return foo; } Ancak fonksiyon şablonlarında kullanılma zorunluluğu yoktur. Bir fonksiyonu tanımlarken fonksiyonun geri dönüş türünü yazdığımız kısma 'auto' yazıyoruz. Devamında fonksiyonun ismini ve parametrelerini. Devamında '->' adet token ekliyoruz. Devamında da ilgili fonksiyonun geri dönüş türünü yazıyoruz. Esas kullanım alanı şablonlar. * Örnek 1, //.. auto func(int x)->double { //.. } * Örnek 2, Fonksiyonun geri dönüş değerinin bir fonksiyon göstericisinin olması durumunda fayda sağlayabilir. //.. int*(*func())(int); // 'func' öyle bir fonksiyon ki; // i. Herhangi bir parametre almamaktadır. // ii. Öyle bir fonksiyon adresi döndürmektedir ki bu döndürülen fonksiyonun geri dönüş değeri 'int*' ve aldığı // parametre 'int' türden. auto funcTwo()->int*(*)(int); // Yukarıdaki ile aynı. * Örnek 3, Şablonlarda kullanımı: Farz edelim ki derleyicinin yazmış olduğu fonksiyona geçilen argümanların değerlerinin toplamının türü ne ise o türden döndürmek istiyoruz. İlgili türü de 'decltype' ile elde edebiliriz. Fakat fonksiyonun geri dönüş değerinin türünün yazıldığı yer fonksiyon skobu içerisinde olmadığından sentaks hatası alacağız. Hadi inceleyelim. //.. template decltype(a+b) func(T a, U b) { //.. BU DURUM SENTAKS HATASIDIR. } template auto foo(T a, U b) -> decltype(a+b) { //.. BU DURUM LEGALDİR. } > Şablonlar ve Jenerik Programlama: Tek kodun birden fazla türe hizmet etmesi durumudur. Temel olarak iki farklı grupta incelenir. Bunlar 'function template' ve 'class template' ki bunlar sırası ile fonksiyon şablonları ve sınıf şablonları şeklinde de isimlendirilebilir. Modern C++ ile dile iki grup daha eklenmiş oldu ki bunlar sırasıyla 'variable template' ve 'alias template' şeklinde gruplardır. >> Bir şablon oluşturmak için 'template' anahtar sözcüğü ve bunu takip eden bir çift '<>' atomu yazmamız gerekmektedir. Bu bir çift '<>' atomunun arasına da bizim şablonumuzun parametre bilgileri geçilmektedir. * Örnek 1, sentaksı anlatan temel bir örnek //.. // Açısal parantez içerisinde yazılanlar birer 'Template Parameter'. Örneğin, buradaki 'T' bir // 'template parameter'. template >>> İş bu şablon parametreleri ise kendi içinde dört gruba ayrılırlar. Bunlar sırasıyla 'Template Type Parameter', 'Template Non-Type Parameter', 'Template Parameter Pack', 'Template Template Parameter' şeklindedirler. >>>> 'Template Type Parameters' : Aşağıdakiler bu gruba girmektedir. >>>>> Şablon parametreleri ise bir türe karşılık gelmektedir. Bir diğer deyişle 'T' bir nevi 'placeholder' görevi görmektedir. Derleyici bu şablonları açarken bu 'T' yerine gerçek türler yazmakta. Örneğin, 'int', 'int*', 'double' vs. Benzer şekilde şablon parametrelerinin yazıldığı alandaki 'class' anahtar sözcüğü ise ilgili 'T' nin bir TÜR OLDUĞUNU söylemektedir, onun bir sınıf türü olduğunu DEĞİL. Dolayısla alternatif olarak bu kelime yerine de 'typename' yazılmaktadır. 'T' nin ayrıca bir 'identifier' olduğunu da unutmayalım. İstersek 'T' yerine 'mustafa' yı da kullanabiliriz. Konvensiyonel olarak 'T', 'U' gibi karakterler kullanılmaktadır. >>>>> Şablon parametreleri istediğimiz kadar olabilir, bir tane olacak diye bir zorlama yoktur. Dolayısıyla bu tip şablonlara da birden fazla tür bilgisini geçmeliyiz, duruma göre. * Örnek 1, //.. // Artık bu şablon ne için kullanılacak ise iki adet tür bilgisi geçmemiz gerekiyor. template >>>>> Derleyicinin derleme zamanında 'T', 'U' gibi harflerin yerine koyacağı gerçek türler için 'Template Type Arguments' denmektedir. Yani Şablon Tür Argümanları şeklinde de söyleyebiliriz. Bu durumda bu 'T' ve 'U' harfleri için 'Template Parameter' denirken, bu türlere karşılık gelen gerçek türlere ise 'Templete Type Arguments' denmektedir. >>>> 'Templete Non-Type Parameter' : Derleyici şablona bakarak yazacağı kodlarda sabitler kullanmasını istiyoruz. İşte 'Template Parameter' olarak bu sabitlere karşılık gelen isimler, 'Template Non Type Parameter' olarak anılırlar. Sabitin türünü yazıp bir isim vererek bu işi yapıyoruz. Tam sayı sabiti veya adres kullanabiliyoruz, gerçek sayı sabiti kullanamıyoruz. * Örnek 1, //.. // Artık 'm' bir 'Template Non Type Parameter'. 'm' isminin olduğu her yerde bir sabit kullanılacak. // 'm' yerine geçecek argümanlar birer 'Constant Expression' olmak zorundadır. template * Örnek 2, //.. template // LEGAL DEĞİL. >>>> Derleme zamanında derleyici 'Template Type Parameter' ve 'Template Non-Type Parameter' için neyin karşılık geleceğini üç farklı araç seti ile öğrenir. Bunlar, >>>>> Bizlerin açık açık derleyiciye bu bilgiyi geçmemiz. Kısaca buna 'Explicit Template Argument' de denmektedir. >>>>> Çıkarım mekanizması kullanılabilir. Derleyici 'T' ye ve/veya 'm' neyin ve nelerin karşılık geleceğini koda bakarak çıkarım yapmakta ('deduction'). Fakat C++17 ye kadar çıkarımlar sadece fonksiyon şablonları için çalışırken, C++17 ile birlikte sınıf şablonları için de çalışır oldu. Bu duruma da 'CTAD' denmektedir, yani 'Class Template Argument Deduction'. * Örnek 1, //.. int main() { int x = 12, y = 23; double xx = 12.21, yy = 23.32; // 'Explicit Template Argument'. Yani bizler derleyiciye açık açık bildirdik 'T' ye karşılık gelecek // ilgili türleri. swap(x,y); swap(xx,yy); // Derleyici çıkarım mekanizmasını kullanaraktan ilgili 'T' parametleri için çıkarımda bulundu. } >>>>> Varsayılan Şablon Argümanı kullanılarak derleyici neyin karşılık geleceğini anlıyor. * Örnek 1, //.. template // Derleyici varsayılan 'Template Type Argument' olarak 'int' ve 'double' türlerini alacaktır eğer // aksi belirtilmez ise. Yani 'T' yerine 'int', 'U' yerine de 'double' gelecektir. >>>> Hadi gelin bu iki şeyi bir örnek ile pekiştirelim: * Örnek 1, //.. int main() { std::array ar; // i. 'Template Type Parameter' olan 'T' yerine 'Template Type Argument' olarak 'int' bilgisi // geçilmiştir. Şablonda 'T' geçen her yerde 'int' kullanılacaktır. // ii. 'Template Non Type Parameter' olarak da '10' sabiti 'Template Non Type Argument' olarak // geçilmiştir. Şablonda artık 'Template Non Type Parameter' geçen her yerde '10' sabiti // kullanılacaktır. Bizler gerek 'Template Type Parameter' bilgisini gerek // 'Template Non-Type Parameter' bilgisini derleyiciye kendi elimiz ile geçmiş // olduk('Explicit Template Argument'). } >>> 'Template Parameter' olarak sadece 'Template Type Parameter' gelebilir, sadece 'Template Non-Type Parameter' gelebilir veya hem 'Template Type Parameter' gelebilir, hem de 'Template Non-Type Parameter' gelebilir. * Örnek 1, //.. // Burada 'T' yerine bir tür, 'm_size' yerine ise 'size_t' türünden bir SABİT gelecektir. template * Örnek 2, template * Örnek 3, template >> Şablon kodlar tipik olarak Başlık Dosyasında bulunur. ODR kuralını da İHLAL ETMEZLER. >> Fonksiyon Şablonlarının detaylı incelenmesi: Fonksiyon şablonları derleyiciye fonksiyon kodları yazdıran şablonlardır. Yukarıdaki gibi bir 'template' başlığından sonra fonksiyonun bildirimi gelmektedir. * Örnek 1 //.. template // T : Template Type Parameter T func(T x, T y) { // İstersek fonksiyonun geri dönüş değeri için kullanılan veya fonksiyonun parametre parantezi içerisinde // kullanılan 'T' için gerçek türleri yazabiliriz. Böylelikle derleyici ona göre fonksiyon şablonunu // açacaktır. } // UNUTULMAMALIDIR Kİ BU BİR FONKSİYON ŞABLONUDUR, BİR FONKSİYON KODU VS. DEĞİLDİR. // YİNE UNUTULMAMALIDIR Kİ ÇALIŞMA ZAMANINDA BU KOD ÇAĞRILMAYACAKTIR. DERLEYİCİNİN ŞABLONU AÇARAK YAZMIŞ OLDUĞU // GERÇEK FONKSİYON ÇAĞRILACAKTIR. >>> İlk aşamada derleyici sadece fonksiyon şablonunu gördüğünden, yani 'Template Type Parameter' gibi şablon parametrelerine hangi türün geçileceğini daha bilmediği anlarda, çok temel seviyede bir sentaks hatalarını inceler. Örneğin, açılan parantezler kapatılmış mı veya bloklar birbiri ile uyumlu mu vs. Ek olarak birde isim arama yapacağı için bulunamayan isimler için de sentaks hatası verecektir. * Örnek 1, template // T : Template Type Parameter T func(T x, T y) { x.foo(); y.func(); return x+y; // Derleyici yukarıda 'x' ve 'y' isimlerinin neye karşılık geldiğini kabaca bilmektedir. Fakat 'foo' ve // 'func' isimlerinin gerçekten 'x' içerisinde olup olmadığını bilemez. Bu fonksiyona öyle bir sınıf // türünden nesne geçilir ki bünyesinde 'foo' fonkiyonlarını barındırır, 'func' fonksiyonlarını // barındırır, 'operator+' fonksiyonunu barındırır vs. x = a+b; // Derleyici burada bir sentaks hatası verecektir çünkü 'a' ve 'b' isimlerinin neye ait olduğunu // bilememiştir. } >>> İkinci aşamada 'Template Type Parameter' olarak hangi gerçek türlerin geldiğini bildiği vakit artık buna yönelik bir kontrol daha yapacaktır. * Örneğin, template // T : Template Type Parameter T func(T x, T y) { // Artık derleyici 'T' yerine hangi gerçek türün geldiğini bildiğinden; x.foo(); // i. Bu satırdaki kod sentaks hatası verdirtecek. y.func(); // ii. Benzer şekilde bu satırdaki kod da sentaks hatası verdirtecek. return x+y; } int main() { func(12,21); // 'Template Type Parameter' olarak 'int' türünün geleceğini bizler 'explicit' olaraksöylemiş olduk. } >>> Fonksiyon şablonlarında türlerin sadece ve sadece 'T' türüne bağlı olmaları şeklinde bir zorunlulukta yoktur. * Örneğin, template // T : Template Type Parameter double func(T x, int a) // Gördüğümüz gibi ikinci parametre bir 'int'. { // Burada 'T' türüne karşılık 'int' türü geliyorsa, 'p' nin türü de 'int*' olmaktadır. T* p; // Burada 'T' türüne karşılık 'int*' türü geliyorsa, 'p' nin türü de 'int**' olmaktadır. } >>> 'Template Type Argument' in ne olduğuna dair bir çıkarım söz konusu olduğunda bu süreç iki farklı şekilde sonuçlanabilir. Ya sentaks hatası alacağız ya da bir türe dair çıkarımda bulunacağız. >>>> Tür çıkarımı yapılırken sentaks hatası olma durumu: Bu tip sentaks hatası da iki farklı nedenden dolayı gerçekleşebilir. Bunlardan ilki fonksiyon çağrı ifadesine bakarak yeterli bilgiye sahip olamaması. Bir diğer hata kaynağı ise fonksiyonun birden fazla parametre değişkeninin olması durumunda gerçekleşmektedir. Derleme zamanında derleyici 'T' nin 'int' olduğunu görürse ilgili şablonu da 'int' türüne göre açacaktır. * Örnek 1, Fonksiyon çağrı ifadesine bakarak yeterli bilgiye sahip olamaması: //.. template void func() { T tx; } int main() { func(); // Derleyici bu ifadeye bakarak 'T' yerine hangi türün geleceğini BİLEMEZ ve bize // "could not deduce template argument for T" minvalinde bir hata verir. Artık bu durumda bizlerin // ya şablonu değiştirmemiz ya da 'explicit' olarak bir tür bilgisini geçmemiz gerekmektedir. func(); // Artık derleyici 'T' yerine 'double' geleceğini biliyor olmaktadır. } * Örnek 2, Fonksiyonun birden fazla parametre değişkenine sahip olması: template // Bu şablonda bir adet 'Template Parameter' vardır ki o da 'T'. // Buradaki fonksiyon şablonunun ise iki adet 'Function Parameter' vardır ki bunlar 'x' ve 'y' dir. void func(T x, T y) { T tx = x+y; } int main() { func(31, 13); // Burada derleyici birinci argümandan hareketle 'T' ye karşılık 'int' geldiğini anlayacaktır. Yine // ikinci argümandan hareketle 'T' ye yine 'int' geldiğini anlayacaktır. Zaten şablon parametresi // olarak da bir adet 'T' kullanıldığından herhangi bir problem de oluşmayacağından, şablon 'int' // türüne göre açılacaktır. func(12, 1.2); // Burada derleyici birinci argümandan hareketle 'T' ye karşılık 'int' geldiğine hükmediyor. Yine // ikinci argümandan hareketle 'T' ye yine 'double' geldiğine hükmediyor. Zaten bir adet şablon // parametresi olduğundan ve bunun iki farklı türe karşılık gelmesi İMKANSIZ olduğundan, 'ambiguity' // tip sentaks hatası alıyoruz. BURADAKİ PROBLEMİN KAYNAĞI FONKSİYONUN YANLIŞ ARGÜMANLAR İLE // ÇAĞRILMASINDADIR. } >>>>> 'Template Argument Deduction' Süreci : 'auto' anahtar sözcüğüne karşılık gelen tür çıkarımı ile bir istisna hariç birebir AYNIDIR. Derleyici 'auto', ki bu bir 'placeholder specifier', için bir tür elde edecek. 'auto' karşılığında gelen türün ne olması gerektiğini, değişkene ilk değer veren ifadeden yola çıkarak anlayacak. Değişen bir şey yok. Fonksiyona gönderilen argümandan 'T' nin ne olduğunu anlayacak. * Örnek 1, //.. template void func(); int main() { auto x = 10; // Burada '10' sabitine bakarak 'auto' ya karşılık gelen türü anlayacak. func(10); // Burada '10' a bakarak 'T' ye karşılık gelen türü anlayacak. } >>>>>> 'auto' anahtar sözcüğü ile yapılan tür çıkarımda üç farklı kural seti vardır. Bunlar, >>>>>>> 'auto' anahtar sözcüğünün yalın halde kullanılması, referans deklaratörü ile birlikte kullanılmaması: >>>>>>> 'auto' anahtar sözcüğünün bir adet referans deklaratörü ile birlikte kullanılması: >>>>>>> 'auto' anahtar sözcüğünün iki adet referans deklaratörü ile birlikte kullanılması: SAĞ TARAF REFERANS ANLAMINA GELMEMEKTEDİR. 'Forwarding Reference' anlamındadır. >>>>>> Fonksiyon Şablon Parametresinin sadece 'T' kullanılması, yani 'auto' anahtar sözcüğünün yalın halde kullanılmasına karşılık gelen senaryo: Aynı kural setidir. Burada çıkarım 'T' için YAPILMAKTADIR. * Örnek 1, template void func(T x) { } int foo(int); int main() { func(/**/); // Bu fonksiyona gönderilen argümanın türünden derleyici 'T' ye karşılık gelen türü anlıyor. func(10); // Argüman olan ifadenin türü 'int' olduğu için 'T' ye karşılık gelen tür de 'int' olacaktır. double dval = 2.3; func(dval); // Argüman olan ifade türü 'double' olduğundan 'T' ye karşılık gelen tür de 'int' olacaktır. const int cival = 123; func(cival); // Tıpkı 'auto' da olduğu gibi kendisinin 'const' olduğu nesneler tür çıkarımında // kullanıldığında, 'const' olma özelliği düşüyor. İşte bu yüzden argüman olan ifade // 'const int' olmasına rağmen 'T' ye karşılık gelen tür 'int' olacaktır. int ival = 123; int& rival = ival; func(rival); // Tıpkı 'auto' da olduğu gibi kendisinin 'reference' olduğu nesneler tür çıkarımında // kullanıldığında, 'reference' olma özelliği düşüyor. İşte bu yüzden argüman olan ifade // 'int&' olmasına rağmen 'T' ye karşılık gelen tür 'int' olacaktır. const int& crival = ival; func(crival); // Tıpkı 'auto' da olduğu gibi kendisinin 'const-reference' olduğu nesneler tür çıkarımında // kullanıldığında, 'const-reference' olma özelliği düşüyor. İşte bu yüzden argüman olan // ifade 'const int&' olmasına rağmen 'T' ye karşılık gelen tür 'int' olacaktır. int a[]{1, 2, 3, 4}; func(a); // Tıpkı 'auto' da olduğu gibi 'array-decay' gerçekleşmekte ve dizi ismi dizinin ilk // elemanının adresine dönmektedir, ki o da 'int*'. Dolayısıyla argüman olan ifade 'int*' // olduğundan, 'T' ye karşılık gelen tür de 'int*' olmaktadır. const ca[]{1, 2, 3, 4}; func(ca); // Tıpkı 'auto' da olduğu gibi 'array-decay' gerçekleşmekte ve dizi ismi dizinin ilk // elemanının adresine dönmektedir, ki o da 'const int*'. Argüman olan ifadenin kendisi // 'const' olmadığından, sadece gösterdiği değer 'const' olduğundan, 'const' özelliği // DÜŞMÜYOR. Dolayısla 'T' ye karşılık gelen tür 'const int*' olmaktadır. func("Can"); // Tıpkı 'auto' da olduğu gibi, argüman olan ifadenin türü 'const char[4]'. 'array-decay' // mekanizmasından dolayı 'cont char*' olmaktadır. Nesnenin kendisi 'const' olmadığından, // tür çıkarımı da, yani 'T' ye karşılık gelen tür de 'const char*' olacaktır. func(foo); // Tıpkı 'auto' da olduğu gibi 'function-to-pointer' dönüşümü gerçekleşmektedir. Dolayısıyla // argüman olan ifadenin türü 'int(*)(int)' şeklinde. İş bu nedenden ötürü de çıkarım yapılan // tür, yani 'T' ye karşılık gelen tür de 'int(*)(int)' olmaktadır. Burada 'foo' // fonksiyonunun türü 'int(int)' şeklinde. Fakat 'function-to-pointer' mekanizması ile // 'int(*)(int)' türüne dönüşmektedir. } * Örnek 2, 'T' ye karşılık gelen türü teyit etme yöntemi: #include template class TypeTeller; // Bir sınıf şablonunun bildirimi, Forward Declaration. template void func(T x) { TypeTeller t; // 'incomplete' tür kullandığımız için derleyici hata verecek. Hata mesajında da 'T' ye // karşılık gelen türü de söylemiş olacak. } int foo(int); int main() { func(10); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int ] */ int a[]{1, 2, 3, 4}; func(a); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int * ] */ const ca[]{1, 2, 3, 4}; func(ca); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=const int * ] */ func("Can"); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=const char * ] */ func(foo); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int (__cdecl *)(int) ] */ } * Örnek 3, //.. template void func(T (*fPtr)(U)); // Burada derleyici 'T' ve 'U' için tür çıkarımı yapmaktadır. Bunlar kesinleştikten sonra 'fPtr' // isimli fonksiyon göstericisinin türü belli olmaktadır. Örneğin, 'T' için 'int' şeklinde ve 'U' // için de 'double' şeklinde tür çıkarımı yaparsa derleyicinin yazdığı iş bu fonksiyonun parametre // değişkeni öyle bir fonksiyon göstericisi olmalı ki "Geri Dönüş Değeri 'int' olan ve Parametresi // 'double'" olan bir fonksiyonu göstermeli. { T tx; // 'tx' in türü 'int' olacak. U uy; // 'uy' nin de türü 'double' olacak. } >>>>>> Fonksiyon Şablon Parametresinin sadece 'T&' kullanılması, yani 'auto&' anahtar sözcüğünün kullanılmasına karşılık gelen senaryo: Aynı kural setidir. 'T' için çıkarım yapılacaktır ve çıkarım başarılı olursa yazılan fonksiyonun parametresi 'L-Value Referance'. * Örnek 1, template void func(T& r) { } int foo(double); int main() { func(10); // 'T' türünün çıkarımı 'int' olarak yapıldı. Fakat fonksiyon şablon parametresinin türü // 'int&' olacağından, yani bir 'L-Value Reference' olmasından ve bu tip referansların da // 'R-Value Expression' lara bağlanamamasından dolayı, SENTAKS HATASI ALIRIZ. int ival = 123; func(ival); // 'T' türü için 'int' yönünde bir çıkarım yapılacak. Fakat fonksiyon şablon parametresinin // türü de 'int&' olacaktır. 'L-Value Referance' tipler 'L-Value Expression' lara // bağlanabileceği için hiç bir sorun yoktur. const int cival = 123; func(cival); // 'T' türü için 'const int' yönünde bir çıkarım yapılacak. Fakat fonksiyon şablon // parametresinin türü de 'const int&' olacaktır. 'L-Value Referance' tipler // 'L-Value Expression' lara bağlanabileceği için hiç bir sorun yoktur. int a[5]{}; func(a); // 'array-decay' mekanizması çalışmayacaktır. İlgili argümanın türü 'int[5]' olduğundan, // 'T' için de 'int[5]' şeklinde tür çıkarımı yapılacaktır. Fakat derleyicinin yazdığı // fonksiyonun parametresinin türü ise beş boyutlu bir diziye referans şeklinde, yani // 'int(&rArray)[5]' şeklinde. func("berker"); // Argüman olan ifadenin türü 'const char[7]' şeklinde. 'array-decay' mekanizması // çalışmayacaktır. Dolayısıyla 'T' için çıkarım yapılan tür de 'const char[7]' şeklinde // olacaktır. Fakat fonksiyonun parametresinin türü ise 'const char(&crArray)[7]' şeklinde // olacaktır. func(foo); // 'function-to-pointer' mekanizması DEVREYE GİRMEYECEKTİR. İlgili 'foo' isimli fonksiyonun // türü 'int(double)' olduğundan, 'T' için çıkarım da 'int(double)' şeklinde olacaktır. // Fakat fonksiyonun parametresinin türü ise 'int(&rFoo)(double)' şeklinde olacaktır. } * Örnek 2, #include template class TypeTeller; // Bir sınıf şablonunun bildirimi, Forward Declaration. template void func(T& x) { TypeTeller t; } int foo(double); int main() { int ival = 123; func(ival); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int ] */ const int cival = 123; func(cival); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=const int ] */ int a[5]{}; func(a); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int [5] ] */ func("berker"); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=const char [7] ] */ func(foo); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int (double) ] */ } * Mülakat Sorusu: //.. template void func(T& x, T& y); int main() { int a[5], b[5], c[6]; func("ali", "nur"); // OK. Birinci argümana bakarak 'T' için 'const char[4]' şeklinde bir çıkarım yapılıyor. İkinci // argümana bakarak da 'T' için 'const char[4]' şeklinde. Aynı türe çıkarım yapıldığından, bir // sorun yoktur. func("emre", "muhammed"); // SENTAKS HATASI. Birinci argümana bakarak 'T' için 'const char[5]' şeklinde bir çıkarım // yapılıyor. İkinci argümana bakarak da 'T' için 'const char[9]' şeklinde. Aynı 'T' türüne // karşılık iki farklı çıkarım yapıldığından dolayı. func(a, b); // OK. Birinci argümana bakarak 'T' için 'int[5]' şeklinde bir çıkarım yapılıyor. İkinci argümana // bakarak da 'T' için 'int[5]' şeklinde. Aynı türe çıkarım yapıldığından, bir sorun yoktur. func(b, c); // SENTAKS HATASI. Birinci argümana bakarak 'T' için 'int[5]' şeklinde bir çıkarım yapılıyor. // İkinci argümana bakarak da 'T' için 'int[6]' şeklinde. Aynı 'T' türüne karşılık iki farklı // çıkarım yapıldığından dolayı. } >>>>>> Fonksiyon Şablon Parametresinin sadece 'T&&' kullanılması, yani 'auto&&' anahtar sözcüğünün kullanılmasına karşılık gelen senaryo: Aynı kural setidir. 'T' için çıkarım yapılacaktır ve başarılı olursa fonksiyonun parametresi 'Forwarding Referance' olacaktır. 'T' türünün çıkarımının nasıl yapıldığını belirleyen şey, fonksiyona gönderilen argüman olan ifadenin 'Value-Category' sidir. >>>>>>> Eğer gönderdiğimiz argüman olan ifade 'L-value' ise, 'T' türünün çıkarımı o türden bir referans olacaktır. Dolayısıyla derleyicinin yazdığı fonksiyonun parametre türü de bir nevi 'reference-to-reference' oluyor. Normalde bir referansın bir başka referansı refere etmesi söz konusu değil fakat bu nokta bir istisna oluyor ve devreye 'Referance Collapsing' kuralları giriyor. >>>>>>>> 'Reference Collapsing' Kuralları: It is permitted to form references to references through type manipulations in templates or typedefs, in which case the reference collapsing rules apply: rvalue reference to rvalue reference collapses to rvalue reference, all other combinations form lvalue reference: * Örnek 1, typedef int& lref; typedef int&& rref; int n; lref& r1 = n; // type of r1 is int& lref&& r2 = n; // type of r2 is int& rref& r3 = n; // type of r3 is int& rref&& r4 = 1; // type of r4 is int&& * Örnek 2, C++ Reference Collapsing happen in 4 contexts; template instantiation. auto type generation. The generation and use of typedefs. Alias declarations. * Örnek 1, template void func (T&& param); //Function template with Universal reference int w; func(w); //calling the template with an L-value argument // i. Here, first T will deduce to type of passed argument. i.e., int&. (L-value). This // will create L-value reference to R-value reference in following statement: // "func(int& && param);" // ii. As per rules, the final type will become L-value (from, L-value to R-value // expression) : func(int & param); >>>>>>> Fakat gönderdiğimiz argüman olan ifade 'R-Value' ise 'T' nin çıkarımı o türden yapılmakta. Dolayısıyla derleyicinin yazdığı fonksiyonun parametre türü de bir nevi 'R-Value Referance' olmaktadır. * Örnek 1, template void func (T&& param); //Function template with Universal reference func(4) // calling the template with R-value // i. First, T will deduce to type of argument. i.e., int&& (R-value). This will create // R-value reference to R-value reference in following statement: "func( int&& && param);" // ii. The final signature will become R-value (from R-value to R-value expression): // "func(int&& param)" * Örnek 1, template void func(T&& r) { } int main() { int y = 100; func(y); // Argüman olan ifadenin değer kategorisi 'L-Value' ve türü 'int'. Dolayısıyla 'T' ye // karşılık gelecek tür 'int&'. Fakat bu durumda fonksiyon parametresinin türü de 'int& &&' // olmakta. Bir nevi 'reference-to-reference'. İşte bu noktada 'Reference Collapsing' // kuralları devreye giriyor ve fonksiyon parametresinin türü 'int&' oluyor. func(45); // Argüman olan ifadenin türü 'int'. 'T' türüne karşılık gelen ifade ise 'int' olmakta. // Fonksiyon parametresinin türü ise bu durumda 'int&&' olmakta. } * Örnek 2, #include template class TypeTeller; // Bir sınıf şablonunun bildirimi, Forward Declaration. template void func(T&& x) { TypeTeller t; } int foo(double); int main() { int x = 100; func(x); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int & ] note: see reference to function template instantiation 'void func(T)' being compiled with [ T=int & ] */ func(45); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int ] */ } * Örnek 3, Argüman olan ifade 'L-Value' ise. #include template void func(T&& x) { // X is a 'L-Value Reference'. x = 200; std::cout << "ii. x : " << x << "\n"; } int main() { int x = 100; std::cout << "i. x : " << x << "\n"; func(x); std::cout << "iii. x : " << x << "\n"; /* # OUTPUT # i. x : 100 ii. x : 200 iii. x : 200 */ } * Örnek 4, Argüman olan ifade bir 'R-Value' ise. #include template void func(T&& x) { // X is a 'R-Value Reference'. std::cout << "ii. x : " << x << "\n"; } int main() { int x = 100; std::cout << "i. x : " << x << "\n"; func(45); std::cout << "iii. x : " << x << "\n"; /* # OUTPUT # i. x : 100 ii. x : 45 iii. x : 100 */ } * Örnek 5.1, #include template class TypeTeller; template void func(T x) { TypeTeller t; } template void funcTwo(T& x) { TypeTeller t; } template void funcThree(T&& x) { TypeTeller t; } class Myclass{}; int main() { int ival = 123; func(ival); // auto => 'void func(T)' being compiled funcTwo(ival); // auto& => 'void funcTwo(T &)' being compiled funcThree(ival); // auto&& => 'void funcThree(T)' being compiled int& rival = ival; func(rival); // auto => 'void func(T) [with T = int]': funcTwo(rival); // auto& => 'void funcTwo(T&) [with T = int]': funcThree(rival); // auto&& => 'void funcThree(T&&) [with T = int&]': const int cival = 123; func(cival); // auto => 'void func(T) [with T = int]': funcTwo(cival); // auto& => 'void funcTwo(T&) [with T = const int]': funcThree(cival); // auto&& => 'void funcThree(T&&) [with T = const int&]': Myclass obj; func(obj); // auto => 'void func(T) [with T = Myclass]': funcTwo(obj); // auto& => 'void funcTwo(T&) [with T = Myclass]': funcThree(obj); // auto&& => 'void funcThree(T&&) [with T = Myclass&]': } * Örnek 5.2, #include template void func(T x) { x *= 2; } int main() { int ival = 123; std::cout << "-----------------------\n"; /* # OUTPUT # ival : 123 ival : 123 */ std::cout << "ival : " << ival << "\n"; func(ival); std::cout << "ival : " << ival << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # 45 : 45 45 : 45 */ std::cout << "45 : " << 45 << "\n"; func(45); std::cout << "45 : " << 45 << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # refIval : 123 refIval : 123 */ int& refIval = ival; std::cout << "refIval : " << refIval << "\n"; func(refIval); std::cout << "refIval : " << refIval << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # refrefIval : 45 refrefIval : 45 */ int&& refrefIval = 45; std::cout << "refrefIval : " << refrefIval << "\n"; func(refrefIval); std::cout << "refrefIval : " << refrefIval << "\n"; std::cout << "-----------------------\n"; } * Örnek 5.3, #include template void funcTwo(T& x) { x *= 2; } int main() { int ival = 123; std::cout << "-----------------------\n"; /* # OUTPUT # ival : 123 ival : 246 */ std::cout << "ival : " << ival << "\n"; funcTwo(ival); std::cout << "ival : " << ival << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # error: cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type ‘int’ */ std::cout << "45 : " << 45 << "\n"; // funcTwo(45); // error: std::cout << "45 : " << 45 << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # refIval : 246 refIval : 492 */ int& refIval = ival; std::cout << "refIval : " << refIval << "\n"; funcTwo(refIval); std::cout << "refIval : " << refIval << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # refrefIval : 45 refrefIval : 90 */ int&& refrefIval = 45; std::cout << "refrefIval : " << refrefIval << "\n"; funcTwo(refrefIval); std::cout << "refrefIval : " << refrefIval << "\n"; std::cout << "-----------------------\n"; } * Örnek 5.4, #include template void funcThree(T&& x) { x *= 2; } int main() { int ival = 123; std::cout << "-----------------------\n"; /* # OUTPUT # ival : 123 ival : 246 */ std::cout << "ival : " << ival << "\n"; funcThree(ival); std::cout << "ival : " << ival << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # 45 : 45 45 : 45 */ std::cout << "45 : " << 45 << "\n"; funcThree(45); std::cout << "45 : " << 45 << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # refIval : 246 refIval : 492 */ int& refIval = ival; std::cout << "refIval : " << refIval << "\n"; funcThree(refIval); std::cout << "refIval : " << refIval << "\n"; std::cout << "-----------------------\n"; /* # OUTPUT # refrefIval : 45 refrefIval : 90 */ int&& refrefIval = 45; std::cout << "refrefIval : " << refrefIval << "\n"; funcThree(refrefIval); std::cout << "refrefIval : " << refrefIval << "\n"; std::cout << "-----------------------\n"; } * Örnek 6, #include template class TypeTeller; template void func(T&& x) { TypeTeller t; } class A{}; int main() { // func(31); // 'void func(T&&) [with T = int]': int x; // func(x); // 'void func(T&&) [with T = int&]': int& rx = x; // func(rx); // 'void func(T&&) [with T = int&]': int&& rrx = 31; // func(rrx); // 'void func(T&&) [with T = int&]': A ax; // func(std::move(ax)); // 'void func(T&&) [with T = A]': } * Örnek 7, #include template class TypeTeller; template void foo(T&& x) { TypeTeller t; } template void func(T&& x) { std::cout << __FUNCSIG__ << " => x : " << x << "\n"; int ival = 13; x = ival; } int main() { // func(31); // void __cdecl func(int &&) => x : 31 // foo(31); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int ] note: see reference to function template instantiation 'void foo(T &&)' being compiled with [ T=int ] */ int x; func(x); // void __cdecl func(int &) => x : 7 foo(x); /* error C2079: 't' uses undefined class 'TypeTeller' with [ T=int & ] note: see reference to function template instantiation 'void foo(T)' being compiled with [ T=int & ] */ } >>>>>> 'auto' daki tür çıkarım ile buradaki tür çıkarım kuralları arasındaki tek farlılık 'std::initializer_list' kullanımında. * Örnek 1, //.. template void func(T x); int main() { auto x = {2, 4, 6, 8}; // Tür çıkarımı 'std::initializer_list' in 'int' açılımı yönünde. func({2, 4, 6, 8}); // Sentaks hatası. } * Mülakat Sorusu: Derleme zamanı sabiti olarak aşağıdaki dizinin boyutunu nasıl elde edebiliriz? //.. // Aşağıdaki diziyi bu fonksiyona geçebilmek için bu şekilde bir fonksiyon parametresi yazdık. // Böylelikle 'rArray' aşağıdaki diziye referanstır. Dolayısıyla 'T' için 'int' gelecek, 'm' için de // '8' rakamı gelecek. template constexpr size_t asize(T(&rArray)[m]) { return m; // İşte bu şekilde bir dizinin boyutunu derleme zamanında C++ usülü hesaplamış olduk. } int main() { int a[] = {1, 2, 3, 4, 5, 6, 7, 8}; /* El-cevap : */ auto size = sizeof(a) / sizeof(a[0]); // C-style ve C++ style /* El-cevap : */ constexpr auto n = asize(a); // 'a' dizisinin türü => int[8]. /* El-cevap : */ constexpr auto m = std::size(a); // C++17. } >>>>>> Varsayılan argüman ifadesinden tür çıkarımı YAPILAMAZ. * Örnek 1, //.. template void func(T x = 10){} int main() { func(); // SENTAKS HATASI. ÇIKARIM YAPILAMAZ. } >>> Aşağıdaki genel örneği bir inceleyelim; * Örnek 1, //.. template void func(F f){ //.. some code here. // Burada 'F' ya bir fonksiyon göstericisi olacak ya da 'operator()()' fonksiyonunu 'overload' etmiş bir // sınıf, ki bu tip sınıflara da 'functor' denmektedir. f(); //.. some code here. } * Örnek 2, Fonksiyon göstericisi olması durumu: //.. template void func(F f){ //.. some code here. f(); //.. some code here. } void foo() { std::cout << "foo cagrildi.\n"; } int main() { func(foo); // OUTPUT => foo cagrildi. } * Örnek 3, Functor olma durumu: //.. class MyFunctor { public: void operator()()const { std::cout << "void MyFunctor::operator()()const cagrildi.\n"; } }; template void func(F f){ //.. some code here. f(); //.. some code here. } int main() { MyFunctor mfc; func(mfc); // OUTPUT => void MyFunctor::operator()()const cagrildi. // func(MyFunctor{}); // Aynı sonucu verecektir. } >>> Derleyici her farklı argüman için ayrı bir fonksiyon yazar. Eğer halihazırda yazılmış bir versiyonu farklı argümanlar ile çağırmak istiyorsak, o yazılanı 'explicit' olarak nitelememiz gerekiyor. * Örnek 1, #include template void func(T mx) { std::cout << "T : "; std::puts(typeid(T).name()); std::cout << "mx : "; std::puts(typeid(mx).name()); } int main() { /* # OUTPUT # T : bool mx : bool T : unsigned char mx : unsigned char T : signed char mx : signed char T : char mx : char T : short mx : short T : int mx : int T : __int64 mx : __int64 T : float mx : float T : double mx : double T : long double mx : long double T : int mx : int */ bool b = true; func(b); unsigned char uc = 'A'; func(uc); signed char sc = 'B'; func(sc); char c = 'C'; func(c); short s = 12; func(s); func(12345); func(12345654321L); func(1.2f); func(1.2); func(1.2L); func(12U); // Bu fonksiyon için yeni bir tane yazılmadı. Daha önce 'int' için yazılan çağrıldı. } >>> Derleyici 'explicit' olarak nitelediğimiz türler için şablonları açacaktır. * Örnek 1, //.. template void func(T mx, U nx) { //.. } int main() { func(12, 4.5); // Her iki argüman için Tür Çıkarımı yapılacaktır. func<>(45, 1.2); // Her iki argüman için Tür Çıkarımı yapılacaktır. func('A', 2.3f); // Buradaki açılım 'int' ve 'double' türlerine göre yapılacaktır. // İlk argüman için bizim belirttiğimiz, ikinci argüman için de Tür Çıkarımı yapılacaktır. funcfunc(3, 4.5); } >>> Fonksiyon şablonlarının 'overload' edilmeleri: Bir fonksiyon şablonu ile aynı isimde bir fonksiyon birbirinin 'overload' durumunda olabilir. Fonksiyon şablonları da birbirlerini 'overload' edebilirler. >>>> Bir şablon ile gerçek bir fonksiyonun birbirini 'overload' etme durumu: * Örnek 1, #include template void func(T x) { std::cout << "Function Template => T is [" << typeid(T).name() << "]\n"; } void func(int x) { std::cout << "func(int)\n"; } int main() { /* # OUTPUT # Function Template => T is [double] Function Template => T is [float] Function Template => T is [char] func(int) */ func(2.3); // i. Derleyici bu çağrı ile karşılaştığında önce şablona bakıyor ve tür bilgisini elde etmeye // çalışıyor. Bu örnek için 'T' için tür çıkarımı 'double' yönünde olacak. // ii. Sonrasında bu 'double' parametreli 'func' fonksiyonu ile 'int' parametreli 'func' fonksiyonunu // 'Function Overload Resolution' a dahil ediyor. // iii. Parametre uyuşması konusunda 'double' parametreli 'exact-match' olduğundan, bu çağrı ona // bağlanıyor. func(2.3f); func('A'); func(999); // Buradan da hareketle diyebiliriz ki parametreler arasında 'exact-match' durumu yok ise şablon o // yeni tür için açılmaktadır. } * Örnek 2, Mülakat Sorusu: Öyle bir fonksiyon oluşturalım ki sadece ve sadece 'int' türden parametre kabul etsin. Diğer türlerden gönderilen argümanlar için SENTAKS HATASI versin. #include template void func(T x) = delete; void func(int x) { std::cout << "func(int)\n"; } int main() { /* # OUTPUT # func(int) */ // func(1.2); // error C2280: 'void func(T)': attempting to reference a deleted function func(12); } >>>>> Fonksiyon şablonlarının birbirlerini 'overload' etme durumu: Çok sık karşımıza çıkan bir durumdur. Hangi 'overload' versiyon şablonunun seçileceğini belirleyen kural setine de 'Partial Ordering Rules' denmektedir. * Örnek 1, #include template // I void func(T) { std::cout << "template func(T)\n"; } template // II void func(T*) { std::cout << "template func(T*)\n"; } int main() { /* # OUTPUT # template func(T*) */ int x{}; func(&x); // Eğer 'II' numaralı fonksiyon tanımı olmasaydı; ilgili fonksiyona gönderilen argümanın türü // 'int*'. Dolayısıyla 'T' nin türü 'int*' şeklinde çıkarım yapıldı. Derleyicinin yazdığı fonksiyonun // parametresi de 'int*' olacaktı. // Eğer 'I' numaralı fonksiyon tanımı olmasaydı; ilgili fonksiona gönderilen argümanın türü 'int*'. // Fakat 'T' nin türü 'int' şeklinde çıkarım yapılacaktı. Derleyicinin yazdığı fonksiyonun // parametresi de 'int*' olacaktı. // Eğer her iki fonksiyon da tanımlı olsaydı, 'II' numaralı versiyon çağrılacaktı. Çünkü kural gereği // DAHA SPESİFİK FONKSİYON PARAMETRESİNE SAHİP VERSİYON SEÇİLİR. } * Örnek 2, #include template // I void func(T) { std::cout << "template func(T)\n"; } template // II void func(T*) { std::cout << "template func(T*)\n"; } template // III void func(T**) { std::cout << "template func(T**)\n"; } int main() { /* # OUTPUT # template func(T**) */ func((int**)0x1200); // Yukarıdaki her üç fonksiyon şablonu da bu çağrıya bağlanabilir. Fakat 'III' numaralı version daha // fazla deklaratöre sahip olduğundan, yani daha spesifik fonksion parametresine sahip olduğundan, o // seçilmiştir. } * Örnek 3, #include template // I void func(T) { std::cout << "template func(T)\n"; } template // II void func(T(&r)[n]) { std::cout << "template T(&r)[n]\n"; } int main() { /* # OUTPUT # template T(&r)[n] */ int a[12]{}; func(a); // 'II' numaralı version seçilecektir çünkü çok daha spesifik. Dolayısıyla 'T' türü adına 'int' türü, // 'n' için de '12' rakamı gelecektir. Eğer sadece 'I' olsaydı, 'T' yerine de 'int*' şeklinde bir // çıkarım yapılacaktı. } >>>>> 'Partial Ordering Rules' : Aşağıdaki örneği inceleyelim. * Örnek 1, #include template // I void func(T) { std::cout << "template func(T)\n"; } template // II void func(T*) { std::cout << "template func(T*)\n"; } int main() { // Round - I // T yerine bir tür seçelim. Varsayalım ki 'int' seçildi. // 'int' türden bir argüman ile 'I' numaralı şablona bir çağrı yapalım. // Bu durumda 'I' numaralı şablonda fonksiyon parametresi 'int' türden olacak. // 'I' numaralı şablondaki fonksiyon parametresi ile 'II' numaralı şablon fonksiyonunu çağırırsak // SENTAKS HATASI OLACAK. Çünkü 'II' numaralı şablon fonksiyonuna 'int' türden bir değer // göndermiş oluyoruz. // Özetle; // i. from main() to function-I succeed. => 'T' in the 'I' is 'int'. // ii. from function-I to function-II failed. => FAILED. // Round - II // T yerine bir tür seçelim. Varsayalım ki 'int' seçildi. // 'int*' türden bir nesne adresi ile 'II' numaralı şablona bir çağrı yapalım. // Bu durumda 'II' numaralı şablonda fonksiyon parametresi 'int*' türden olacak. // 'II' numaralı şablondaki fonksiyon parametresi ile 'I' numaralı şablon fonksiyonunu çağırırsak // SENTAKS HATASI OLMAYACAK. // Çünkü 'I' numaralı fonksiyona 'int*' türden bir değer göndermiş oluyoruz ve 'I' numaralı // fonksiyon şablonunu için 'T' için tür çıkarımı 'int*' yönünde yapılmış oluyor. // Özetle, // i. from main() to function-II succeed. => 'T' in the 'II' is 'int'. // ii. from function-II to function-I succeed. => 'T' in the 'II' is 'int*'. // Eğer Round-I ve Round-II SENTAKS HATASI VERMESEYDİ, 'ambiguity' tip SENTAKS HATASI alacaktık. } >>> 'Perfect Forwarding' : Modern C++ ile dile gelen en önemli özelliklerden birisidir. Gerçek bir fonksiyona yapacağım çağrı ile fonksiyon şablonuna, ki bu da arka planda aynı gerçek fonksiyonu çağıracaktır, yapacağım çağrı arasında bir fark OLMASIN İSTİYORUM. * Örnek 1, #include void foo(int& x) { std::cout << "foo(int&) \n"; } // I void foo(const int& x) { std::cout << "foo(const int&) \n"; } // II void foo(int&& x) { std::cout << "foo(int&&) \n"; } // III int main() { // Ben foo işlevine hangi argüman ile çağrı yaparsam, // bununla benim bu argümanı 'func' fonksiyonuna göndermem ve 'func' fonksiyonunun bu argüman ile 'foo' // fonksiyonunu çağırması arasında bir fark OLMAMALI. // Value-Category ve 'const' gibi özellikleri KORUNMUŞ OLACAK. /* # OUTPUT # foo(int&) foo(const int&) foo(int&&) */ int a{}; foo(a); // 'III' numaralı 'viable' DEĞİL. 'I' numaralı 'exact-match' olduğundan çağrılacaktır. const int& cx{5}; foo(cx); // 'II' numaralı çağrılacaktır. foo(34); // 'III' numaralı çağrılacaktır. // Peki bizler öyle bir fonksiyon şablonu yazalım ki o şablona geçeceğimiz parametreler, yine bu // örnekteki çıktıyı versin; El-cevap: aşağıdaki cevapları inceleyelim. } * Örnek 2, * Örnek 2.0 : Yazacağımız fonksiyon şablonunun parametresi "const T& x" şeklinde olursa: #include void foo(int& x) { std::cout << "foo(int&) \n"; } // I void foo(const int& x) { std::cout << "foo(const int&) \n"; } // II void foo(int&& x) { std::cout << "foo(int&&) \n"; } // III template void func(const T& x) { foo(x); } int main() { /* # OUTPUT # ------- foo(int&) foo(const int&) ------- foo(const int&) foo(const int&) ------- foo(int&&) foo(const int&) ------- */ std::cout << "-------\n"; int a{}; foo(a); func(a); std::cout << "-------\n"; const int& cx{5}; foo(cx); func(cx); std::cout << "-------\n"; foo(34); func(34); std::cout << "-------\n"; // ÇIKTITAN DA GÖRÜLDÜĞÜ ÜZERE BU ŞEKİLDEKİ BİR ŞABLON İŞE YARAMADI. } * Örnek 2.1 : Yazacağımız fonksiyon şablonunun parametresi "T x" şeklinde olursa: #include void foo(int& x) { std::cout << "foo(int&) \n"; } // I void foo(const int& x) { std::cout << "foo(const int&) \n"; } // II void foo(int&& x) { std::cout << "foo(int&&) \n"; } // III template void func(T x) { foo(x); } int main() { /* # OUTPUT # ------- foo(int&) foo(int&) ------- foo(const int&) foo(int&) ------- foo(int&&) foo(int&) ------- */ std::cout << "-------\n"; int a{}; foo(a); func(a); std::cout << "-------\n"; const int& cx{5}; foo(cx); func(cx); std::cout << "-------\n"; foo(34); func(34); std::cout << "-------\n"; } * Örnek 2.2 : Yazacağımız fonksiyon şablonunun parametresi "T&& x" şeklinde olursa: #include void foo(int& x) { std::cout << "foo(int&) \n"; } // I void foo(const int& x) { std::cout << "foo(const int&) \n"; } // II void foo(int&& x) { std::cout << "foo(int&&) \n"; } // III template void func(T&& x) { foo(x); } int main() { /* # OUTPUT # ------- foo(int&) foo(int&) ------- foo(const int&) foo(const int&) ------- foo(int&&) foo(int&) ------- */ std::cout << "-------\n"; int a{}; foo(a); func(a); std::cout << "-------\n"; const int& cx{5}; foo(cx); func(cx); std::cout << "-------\n"; foo(34); func(34); std::cout << "-------\n"; } * Örnek 3, ÇÖZÜM : 'std::forward' isimli fonksiyon şablonunun geri dönüş değerini, ki bu da 'std::move' gibi dönüştürmeye işlemi yapıyor, bizim 'func' isimli fonksiyon şablonumuzda çağrı yapıyoruz. #include void foo(int& x) { std::cout << "foo(int&) \n"; } // I void foo(const int& x) { std::cout << "foo(const int&) \n"; } // II void foo(int&& x) { std::cout << "foo(int&&) \n"; } // III template void func(T&& x) { foo(std::forward(x)); } int main() { /* # OUTPUT # ------- foo(int&) foo(int&) ------- foo(const int&) foo(const int&) ------- foo(int&&) foo(int&&) ------- */ std::cout << "-------\n"; int a{}; foo(a); func(a); std::cout << "-------\n"; const int& cx{5}; foo(cx); func(cx); std::cout << "-------\n"; foo(34); func(34); std::cout << "-------\n"; // KISSADAN HİSSE : BİR ŞABLON FONKSİYONUNUN 'Template Type Argument' İ OLARAK 'T&&' SEÇİYORSAK, TEK // AMACIMIZ 'Perfect Forwarding' OLMALIDIR. Bu şekil bir fonksiyon bildirimi görmüşsek şundan eminiz ki // ilgili fonksiyonun bloğunda 'std::forward' fonksiyonunun 'T' türünden açılımı kullanılmıştır. } * Örnek 4, #include void func(const int& x) { std::cout << "func(const int&)\n"; } void func(int&& x) { std::cout << "func(int&&)\n"; } template void f1(T&& a) { f2(std::forward(a)); } template void f2(T&& a) { f3(std::forward(a)); } template void f3(T&& a) { func(std::forward(a)); } int main() { f1(12); // OUTPUT => func(int&&) int x{}; f1(x); // OUTPUT => func(const int&) } * Örnek 5, #include class Data{ public: Data() = default; Data(const Data&) { std::cout << "copy ctor\n"; } Data(Data&&) { std::cout << "move ctor\n"; } }; void func(const Data& x) { Data mydata(x); } void func(Data&& x) { Data mydata(std::move(x)); } int main() { Data mydata; func(mydata); // OUTPUT => copy ctor func(Data{}); // OUTPUT => move ctor } >>>> 'std::vector' sınıfındaki '.emplace_back()' fonksiyonu bu yapıda çalışmaktadır. Bu fonksiyonlara geçilecek argümanlar, vektörde tutulan sınıfın 'Ctor.' fonksiyonuna gönderilmekte. Dolayısıyla bizler bir nevi iglili sınıfın 'Ctor.' fonksiyonunun istediği argümanları geçiyoruz. >>>> Mülakat Sorusu: * Örnek 1, Aşağıdaki iki fonksiyonun varlık nedenini ve ikisi arasındaki farkı açıklayınız. class Data{}; void func(Data&& x) {} // Burada bir şablon kullanılmadığından tür çıkarımı söz konusu değil. Dolayısıyla 'x' in türü 'Data&&'. // Yani bir 'R-Value Referance'. Böyle bir fonksiyonun tek varlık nedeni 'Move Semantics'. template void func(T&& x){} // Burada bir şablon kullanılmakta. Dolayısıyla 'x' bir 'R-Value Referance' DEĞİL. 'x' bir // 'Forwarding Reference'. Böyle bir fonksiyonun tek varlık nedeni almış olduğu argümanı başka bir // fonksiyona PAS ETMEKTİR. >>> 'Variadic Function Templates' : 'Template Type Parameter' in bulunduğu yeri bu sefer şu şekilde yazıyoruz; "template". 'myVariadicTemplate' ismi için de artık 'Template Parameter Pack' denmektedir. Derleyicinin yazdığı fonksiyonun parametresine de 'Function Parameter Pack' denmektedir. İş bu 'Function Parameter Pack' eğer; >>>> Yalın halinde kullanılırsa, bu paket içindeki türler yalın türler olmaktadırlar. >>>> Yanlarında bir referans deklaratörü olmasu durumunda bu paket içindeki türler birer 'L-Value Referance' halini almaktadır. >>>> Eğer 'const' anahtar sözcüğü ve referans deklaratörü ile birlikte kullanılırsa da paket içindeki türler 'const L-Value Referance' halini alırlar. >>>> Eğer yanlarında sadece iki adet referans deklaratörü ile birlikte kullanılırsa da bu paket içindeki türler 'Forwarding Referance' halini almaktadır. * Örnek 1, template void foo(MyVariadicTemplate... myOpenedVariadicTemplate) { } // Burada, // 'Template Parameter Pack' : MyVariadicTemplate // 'Function Parameter Pack' : myOpenedVariadicTemplate // isimlerine karşılık gelmektedir. int main() { foo(2,5); // 'Function Parameter Pack' içindeki türler birer 'primitive' türler. } * Örnek 2, template void foo(MyVariadicTemplate& ... myOpenedVariadicTemplate) { } int main() { foo(2, 5); // SENTAKS HATASI. Çünkü 'Function Parameter Pack' içindeki türler birer 'L-Value Reference' şeklindeler. // Dolayısıyla bunlara 'R-Value Expression' bağlanamazlar. } * Örnek 3, template void foo(cosnt MyVariadicTemplate& ... myOpenedVariadicTemplate) { } int main() { foo(2, 5); // 'Function Parameter Pack' içindeki türler birer 'const-L-Value Reference' şeklindeler. Dolayısıyla // bunlara 'R-Value Expression' bağlanamazlar. } * Örnek 4, template void foo(MyVariadicTemplate&& ... myOpenedVariadicTemplate) { } int main() { foo(2, 5); // 'Function Parameter Pack' içindeki türler birer 'Forwarding Reference'. } >>>> GCC derleyicinde 'PrettyFunction' isminde bir makro var. Fonksiyonun imzasını içeren bir 'string' e açılmaktadır. MSVC derleyicisinde de bu makronun karşılığı '__FUNCSIG__' makrosudur. * Örnek 1, #include template void func(MyVariadicTemplate... myOpenedVariadicTemplate) { std::cout << "[ " << __FUNCSIG__ << " ]\n"; } int main() { func(2,5); // OUTPUT => [ void __cdecl func(int,int) ] } * Örnek 2, #include template void func(MyVariadicTemplate& ... myOpenedVariadicTemplate) { std::cout << "[ " << __FUNCSIG__ << " ]\n"; } int main() { int x = 2; double d = 2.2; func(x, d); // OUTPUT => [ void __cdecl func(int &,double &) ] // Çıkarım Yapılan Tür | Fonksiyonun Parametreleri } * Örnek 3, #include template void func(MyVariadicTemplate&& ... myOpenedVariadicTemplate) { std::cout << "[ " << __FUNCSIG__ << " ]\n"; } int main() { int x = 2; func(x, 1.2); // OUTPUT => [ void __cdecl func(int &,double &&) ] // Fonksiyonun birinci parametresi 'int&'. Çünkü fonksiyona argüman olarak bir 'L-Value' geçildi. // Fonksiyonun ikinci parametresi 'int&&'. Çünkü fonksiyona argüman olarak bir 'R-Value' geçildi. // Birinci 'Template Type Argument' => 'int&' // İkinci 'Template Type Argument' => 'int' } * Örnek 4, #include template void func(T x) { std::cout << "[ " << __FUNCSIG__ << " ]\n"; } template void funcRef(T& x) { std::cout << "[ " << __FUNCSIG__ << " ]\n"; } template void funcRefRef(T&& x) { std::cout << "[ " << __FUNCSIG__ << " ]\n"; } int main() { // A : 'T' yerine gelen tür. // B : Fonksiyon parametresi olan 'x' in türü. std::cout << "\n*******************************\n"; // A | B func(31); // OUTPUT => [ void __cdecl func (int) ] int x = 100; func(x); // OUTPUT => [ void __cdecl func (int) ] int& rx = x; func(rx); // OUTPUT => [ void __cdecl func (int) ] int&& rrx = 32; // OUTPUT => [ void __cdecl func (int) ] const int& crx = x; func(crx); // OUTPUT => [ void __cdecl func (int) ] std::cout << "*******************************\n"; // funcRef(100); // SENTAKS HATASI. int xx = 200; funcRef(xx); // OUTPUT => [ void __cdecl funcRef(int &) ] int& rxx = xx; funcRef(rxx); // OUTPUT => [ void __cdecl funcRef(int &) ] int&& rrxx = 400; funcRef(rrxx); // OUTPUT => [ void __cdecl funcRef(int &) ] const int& crxx = xx; funcRef(crxx); // OUTPUT => [ void __cdecl funcRef(const int &) ] std::cout << "*******************************\n"; funcRefRef(300); // OUTPUT => [ void __cdecl funcRefRef(int &&) ] int xxx = 200; funcRefRef(xxx); // OUTPUT => [ void __cdecl funcRefRef(int &) ] int& rxxx = xxx; funcRefRef(rxxx); // OUTPUT => [ void __cdecl funcRefRef(int &) ] int&& rrrxxx = 400; funcRefRef(rrrxxx); // OUTPUT => [ void __cdecl funcRefRef(int &) ] // OUTPUT => [ void __cdecl funcRefRef(const int &) ] const int& crxxx = xxx; funcRefRef(crxxx); std::cout << "*******************************\n"; } >>>> Gerek 'Template Parameter Pack' gerek 'Function Parameter Pack' öğe sayısını bize 'compile time' sabiti olarak verecek bie operatör vardır; C++11 ile dile eklenen yeni bir operatördür. 'sizeof' anahtar kelimesinden sonra '...' atomunu yazıyoruz. Devamında da parantez içerisine bu iki parametre paketinden birini yazıyoruz. * Örnek 1, #include template void func(MyVariadicTemplate ... myOpenedVariadicTemplate) { constexpr auto n1 = sizeof...(MyVariadicTemplate); constexpr auto n2 = sizeof...(myOpenedVariadicTemplate); std::cout << "n1 : " << n1 << "\n"; std::cout << "n2 : " << n2 << "\n"; } int main() { func(1, 5, 6.7, 7.6); } >>>> Parametre paketlerinin açılış şekilleri; * Örnek 1, #include template void func(Args...args) { std::tuple myTuple; // Derleyici fonksiyon çağrısındaki argümanlara bakarak 'Args...' deyimini 'int, double, long' // şeklinde açacaktır. // std::tuple myTuple; } template void foo(Args...args) { int a[] = {args...}; // 'args...' ifadesini derleyici virgüller ile ayrılmış ve elemanları paketin bir elemanı olan // listeye çevirmekte. } /* // 'main' içerisindeki 'foo' çağrısını gören derleyici aşağıdaki gibi bir kod yazacaktır. void foo(int p1, int p2, int p3) { int a[] = {p1, p2, p3}; } */ template void myFoo(Args...args) { fooAnother(args)... } /* // 'main' içerisindeki 'myFoo' çağrısını gören derleyici aşağıdaki gibi bir kod yazacaktır. void myFoo(int p1, double p2, long p3) { fooAnother(p1), fooAnother(p2), fooAnother(p3) } */ // Yukarıdaki şablonlar ışığında, derleyici, aşağıdaki şablonu neye dönüştürecektir? template void myFunc(Args&& ...args) { fooAnotherTwo(std::forward(args)...); } /* // İŞTE AŞAĞIDAKİ GİBİ BİR KOD YAZACAKTIR. void func(int&& p1, double&& p2, long&& p3) { fooAnotherTwo( std::forward(p1), std::forward(p2), std::forward(p3) ); } */ int main() { func(10, 3.4, 5L); foo(1, 2, 3); myFoo(1, 2.2, 5L); myFunc(10, 3.4, 5L); } >>>>> Derleme zamanı Öz-yinelemeli yöntem ile paketin açılması: Derleyiciye derleme zamanında 'n' tane fonksiyon kodu yazdırılmasıdır. * Örnek 1, template void print(const T& r) { std::cout << r << "\n"; } template void print(const T& r, const Args& ... args) { // std::cout << __FUNCSIG__ << "\n\n"; print(r); print(args...); } int main() { int x = 10; double d = 23.5; print(x, d, 9L, 'a'); } // Derleyici yukarıdaki 'print' fonksiyonlarını aşağıdaki şekilde yazmaktadır: /* // Round - I void print(int x, double p1, long p2, char p3) { print(x); print(p1, p2, p3); } */ /* // Round - II void print(double x, long p1, char p2) { print(x); print(p1, p2); } */ /* // Round - III void print(long x, char p1) { print(x); print(p1); } */ // PARAMETRE PAKETİNİN AÇILIMI BURADA ARTIK BİTMİŞTİR. * Örnek 2, #include #include template T summer(T v) { return v; } template T summer(const T& first, const Args&... args) { return first + summer(args...); } int main() { /* # OUTPUT # 157 NecAtiErgingin */ std::cout << summer(10, 20, 30, 80, 17) << "\n"; std::string s1 = "Nec", s2 = "Ati", s3 = "Ergin"; std::cout << summer(s1, s2, s3, "gin") << "\n"; } >>>>> 'std::make_unique' fonksiyonunun temsili bir implementasyonu: * Örnek 1, #include class Data{ public: Data(int, double, long){} }; template std::unique_ptr MakeUnique(Args&& ... args) { return std::unique_ptr{ new T(std::forward(args)...)}; // | T türünden dinamik ömürlü bir nesne oluşturduk, // | onun Ctor. fonksiyonuna argümanlar gönderdik. // | std::unique_ptr sınıf türünden geçici bir türü nesne oluşturduk. // | Bu geçici nesneyi de dönrdürdük. } int main() { auto ptr = MakeUnique(12, 3.4, 34L); } >>> Gerçek sınıflarımızın üye fonksiyonları da birer fonksiyon şablonları olabilir. Bu üye fonksiyonlara özel üye fonksiyonlar da dahildir. * Örnek 1, #include #include class Myclass{ public: template void func(T x){ std::cout << ">>> " << __FUNCSIG__ << " \n"; } template void funcRef(T& x){ std::cout << ">>> " << __FUNCSIG__ << " \n"; } template void funcRefRef(T&& x){ std::cout << ">>> " << __FUNCSIG__ << " \n"; } }; int main() { Myclass mx; std::cout << "------------------------------------------------------------\n"; mx.func(12); // >>> void __cdecl Myclass::func(int) // mx.funcRef(12); // Sentaks hatası. mx.funcRefRef(12); // >>> void __cdecl Myclass::funcRefRef(int &&) std::cout << "------------------------------------------------------------\n"; int ival = 200; mx.func(ival); // >>> void __cdecl Myclass::func(int) mx.funcRef(ival); // >>> void __cdecl Myclass::funcRef(int &) mx.funcRefRef(ival); // >>> void __cdecl Myclass::funcRefRef(int &) std::cout << "------------------------------------------------------------\n"; int& rival = ival; mx.func(rival); // >>> void __cdecl Myclass::func(int) mx.funcRef(rival); // >>> void __cdecl Myclass::funcRef(int &) mx.funcRefRef(rival); // >>> void __cdecl Myclass::funcRefRef(int &) std::cout << "------------------------------------------------------------\n"; const int cival = 300; const int& crival = cival; mx.func(cival); // >>> void __cdecl Myclass::func(int) mx.funcRef(crival); // >>> void __cdecl Myclass::funcRef(const int &) mx.funcRefRef(crival); // >>> void __cdecl Myclass::funcRefRef(const int &) std::cout << "------------------------------------------------------------\n"; int&& rrival = 500; mx.func(rrival); // >>> void __cdecl Myclass::func(int) mx.funcRef(rrival); // >>> void __cdecl Myclass::funcRef(int &) mx.funcRefRef(rrival); // >>> void __cdecl Myclass::funcRefRef(int &) std::cout << "------------------------------------------------------------\n"; } >> Sınıf Şablonlarının detaylı incelenmesi: Fonksiyon şablonu oluştururken fonksiyon ismini, geri dönüş değerini ve parametre parantezini yazdığımız satıra artık sınıfımızın tanımını yazmaktayız. * Örnek 1, //.. template class Myclass{ public: T func(T x){} private: T mx; }; // Yukarıdaki sınıf şablonuna bakarak 'T' ye karşılık gelen her bir tür için sınıf şablonu yazılacaktır. >>> Derleyici, çağırmadığımız üye fonksiyonun kodunu YAZMAMAKTADIR. * Örnek 1, //.. template class Myclass{ public: T func(T x){} void foo(T x, int m) {} private: T mx; }; int main() { Myclass m1; // Derleyici 'int' türüne açılan bir sınıf şablonu yazacak fakat sadece 'Default Ctor.' ve 'Dtor.' // fonksiyonlarının kodunu yazacaktır. 'func' fonksiyonunun kodunu yazmayacaktır. m1.func(); // Şimdi sadece 'func' fonksiyonunun kodunu yazmıştır. 'foo' fonksiyonun kodunu yine yazmayacaktır. } >>> Sınıf şablonlarında 'data members' ve 'member functions' lar için de şablon parametresi kullanılabilir. * Örnek 1, template class Neco{ public: void myFunc(T (*myFuncPtr)(U)); private: T my_array[m]{}; }; int main() { Neco mx; } >>> Sınıf şablonlarının kendisi bir SINIF DEĞİLDİR. * Örnek 1, template class Myclass{ }; int main() { Myclass mx; // SENTAKS HATASI. } >>> Aynı sınıf şablonunun türetilen sınıflar arasında OTOMATİK BİR DÖNÜŞÜM YOKTUR. * Örnek 1, template class Myclass{ }; int main() { Myclass ival; Myclass dval; ival = dval; // SENTAKS HATASI. İKİSİ FARKLI SINIF OLDUĞUNDAN ARALARINA OTOMATİK DÖNÜŞÜM YOKTUR. } * Örnek 2, bu kural sınıfların fonksiyonları için de geçerlidir. template class Nec{ public: void func(Nec x); }; int main() { Nec<1> nx; Nec<1> nxx; Nec<2> ny; nx.func(ny); // SENTAKS HATASI. nx.func(nxx); // LEGAL. } >>> Gerçek sınıfları ne şekilde kullanabiliyorsak, sınıf şablonlarından türetilen sınıfları da aynı yerlerde kullanabiliriz. * Örnek 1, //.. #include #include template class Neco{}; template void func(T x) { std::cout << typeid(T).name() << "\n"; } template void foo(Neco x) { std::cout << typeid(T).name() << "\n"; } int main() { Neco nx; func(nx); // OUTPUT => class Neco foo(nx); // OUTPUT => float } >>> Sınıf şablonlarını iç içe de açtırabiliriz. * Örnek 1, //.. template class Neco{}; int main() { Neco> nx; // 'nx' öyle bir sınıf türünden nesne ki elemanları 'Neco' türünden. } * Örnek 2, STL de dinamik dizilerin dizilerini oluşturmada da kullanılır. //.. int main() { // => 1D dizi. std::vector ivecOne; // Öyle bir vektör ki elemanları da 'int' tutan bir vektör. // => 2D dizi. std::vector> ivecTwo; // => 3D dizi. std::vector>> ivecThree; } >>> Sınıflara ait üye şablon sınıfların tanımını gerek 'inline' olarak sınıfın tanımının içinde GEREK SINIFIN DIŞINDA YAPABİLİRİZ. * Örnek 1, 'inline' olarak sınıfın içerisinde yapılması: #include template class Counter{ public: Counter() = default; Counter(T x) : cnt{x}{} void set(T val) { cnt = val; } void get()const { return cnt; } void print()const { std::cout << "[" << cnt << "]\n"; } Counter& operator++() { ++cnt; return *this; } Counter operator++(int) { Counter temp{*this}; ++*this; return temp; } friend std::ostream& operator<<(std::ostream& os, const Counter& x) { return os << "[" << x.cnt << "]\n"; } private: T cnt{}; }; int main() { /* # OUTPUT # [34] [35] [87135] */ Counter cx; cx.set(34); std::cout << cx; ++cx; cx.print(); Counter cy{ 87134 }; ++cy; std::cout << cy << "\n"; } * Örnek 2, tanımlamalarının sınıfın dışında yapılması. Buradaki unutulmaması gereken nokta tanımlar yine başlık dosyasında. Gerçek sınıf kodlarındaki gibi '.cpp' uzantılı dosyada değil. #include template class Counter{ public: Counter() = default; Counter(T x); void set(T val); void get()const; void print()const; Counter& operator++(); Counter operator++(int); friend std::ostream& operator<<(std::ostream& os, const Counter& x); private: T cnt{}; }; template Counter::Counter(T x) : cnt{x} {} template void Counter::set(T x) { Counter temp; xnt = x; } // Sınıfın üye fonksiyonunda sınıfın kendisini kullanırken tekrardan '<>' şeklinde açılım yapılmasına gerek // yok. Örneğin, 'temp' nesnesinin kullanımı LEGALDİR. Çünkü fonksiyonun bloğu ve parametre parantezi // 'class-scope' içerisindedir. Fakat fonksiyonun geri dönüş değerinin türünün yazıldığı yerde, '<>' // NİTELEMESİNİ YAPMAK ZORUNDAYIZ. template void Counter::get() const { return cnt; } // Diğer fonksiyonları da yukarıdaki şekilde tek tek tanımlamalıyız. >>> 'std::pair' sınıf şablonunun temsili bir implementasyonu: * Örnek 1, #include #include template struct Pair{ Pair() = default; Pair(const T& t, const U& u) : first{t}, second{u} {} T first{}; U second{}; }; // Standart küpüthanedeki 'pair' sınıfının 'operator<<' fonksiyonu 'overload' EDİLMEMİŞ, yani öyle bir // sınıfı yok. template std::ostream& operator<<(std::ostream& os, const Pair& p) { return os << "[" << p.first << "|" << p.second << "]\n"; } int main() { /* # OUTPUT # [0|0] [0|0] [987|Ahmet] [31|C] [[0|0] |[|] ] */ Pair p; std::cout << "[" << p.first << "|" << p.second << "]\n"; Pair px{12, 4.5}; std::cout << "[" << p.first << "|" << p.second << "]\n"; Pair pxx{987, "Ahmet"}; std::cout << pxx; Pair pxxx{ 31, 'C'}; std::cout << pxxx; Pair< Pair , Pair> idCard{}; std::cout << idCard; } >>> Standart kütüphanedeki 'std::pair' sınıfının incelenmesi: >>>> Kendileri, 'utility' başlık dosyasında tanımlanmıştır. Kendi oluşturacağımız 'nutility' başlık dosyasına eklenecek ilk fonksiyonumuz örnektedir. * Örnek 1, #include template std::ostream& operator<< (std::ostream& os, const std::pair& px) { return os << "[" << px.first << "," << px.second << "]"; } >>>> C++17 ile tür çıkarımı 'Ctor.' ile Sınıf Şablonlarına da geldi. Fakat öncesinde tür çıkarımı nasıl yapılıyordu bir bakalım; * Örnek 1, #include #include #include using namespace std; template std::ostream& operator<< (std::ostream& os, const std::pair& px) { return os << "[" << px.first << "," << px.second << "]"; } template std::pair MakePair(const T& t, const U& u) { return std::pair{t, u}; } int main() { /* # OUTPUT # [12,5.6] [[A,12],[alican,4.5]] */ auto p = MakePair(12, 5.6); std::cout << p << "\n"; // Orjinal Hali => make_pair(12, 5.6); auto pp = make_pair( make_pair('A', 12), make_pair("alican", 4.5) ); std::cout << pp << "\n"; } >>>> İki 'std::pair' arasındaki karşılaştırma kriteri şu şekildedir: 'first' değeri küçük olan 'pair' küçüktür. Eğer 'first' değerleri eşitse 'second' değeri küçük olan küçüktür. Hadi gelin standarttaki 'pair' sınıfında olan 'operator<' fonksiyonu nasıl implemente edilmiş. * Örnek 1, template bool operator<(const std::pair& px, const std::pair& py) { return px.first < py.first || ( !(py.first < px.first) && px.second < py.second ); // Buradaki, // I: Kısa devre mekanizmasını tetiklemesi için yazılmıştır. 'first' değeri küçük ise o kısım 'true' // olacak ve deyimin geri kalanı HESAPLANMAYACAKTIR. // II ve III : Buradaki amaç '==' operatörünü kullanmamak, sadece '<' operatörünü kullanmak. Belki // 'T' veya 'U' türleri için 'operator==' overload edilmemişse diye. } >>>> Ayrıca yine iş bu 'std::pair' sınıfının 'swap' fonksiyonu vardır. >>>> 'std::pair' sınıfının 'Copy Semantics' fonksiyonları bir 'Member Template' olmasından ötürü, 'std::pair' nesneleri birbirine KOPYALANABİLMEKTEDİR. Fakat unutulmamalıdır ki 'std::pair' sınıfının öğeleri de birbirine atanabilir olması gerekmektedir. * Örnek 1, //.. class Data{}; int main() { std::pair px; std::pair py; px = py; // SENTAKS HATASI. Çünkü 'Data' sınıfı 'std::string' sınıfına BU HALİ İLE ATANAMAZ. } >>>>> 'Member Template' : Sınıf şablonlarının bünyelerinde fonksiyon şablonları barındırmaları. * Örnek 1, template class Neco{ public: void func(Neco); // Gerçek bir fonksion. }; int main() { Neco nx; Neco ny; nx.func(ny); // GEÇERSİZ. Çünkü her iki nesne de birbirinden farkı. } * Örnek 2, #include template class Neco{ public: template void func(Neco x) // Member template { std::cout << "type of *this : " << typeid(*this).name(); std::cout << "\ntype of param : " << typeid(x).name(); } }; int main() { /* # OUTPUT # type of *this : class Neco type of param : class Neco */ Neco nx; Neco ny; nx.func(ny); // Artık legal. } * Örnek 3, Daha önce yazılan 'Counter' sınıfına, bu mekanizmanın dahil edilmesi. #include template class Counter{ public: Counter() = default; Counter(T x) : cnt{x}{} void set(T val) { cnt = val; } T get()const { return cnt; } void print()const { std::cout << "[" << cnt << "]\n"; } Counter& operator++() { ++cnt; return *this; } Counter operator++(int) { Counter temp{*this}; ++*this; return temp; } template Counter operator=(const Counter& other) { cnt = other.get(); return *this; } friend std::ostream& operator<<(std::ostream& os, const Counter& x) { return os << "[" << x.cnt << "]\n"; } private: T cnt{}; }; int main() { Counter cx{21}; Counter cy{}; cx = cy; } >> Birden fazla 'Template Type Parameter' olması, bunlara karşılık gelen argümanların farklı türler olması zorunluluğunu da beraberinde GETİRMİYOR. * Örnek 1, #include template void func(T x, U y); int main() { func(12, 21); // LEGAL. 'T' ve 'U' ya karşılık 'int' gelmiştir. } template void func(T x, U y) { std::cout << "x + y : " << x + y << "\n"; } >> C++ Templates, by Joutistiss. C++17 için yeniden yazıldı. >> 'template' KODLAR BAŞLIK DOSYALARINDA BULUNUR. Sadece implementasyonu ilgilendiren, sadece o '.cpp' dosyasında kullanılacak şablonlar, başlık dosyasında olmayabilir. Sadece aşağıdaki gibi görsel bir ayrım yapabiliriz: * Örnek 1, // Neco.h template T sum(T x, T y); #include "Neco.hpp" // Neco.hpp template T sum(T x, T y) { } // main.cpp //.. #include "Neco.h" >> Fonksiyon şablonlarının ve sınıf şablonlarının varsayılan (default) template argüman almaları: * Örnek 1, template class Myclass {}; int main() { Myclass m; // C++17 ve sonrasıdaki dönem için LEGAL. Evvelki dönemlerde SENTAKS HATASI. Myclass<> mx; // 'int' açılımı yapılacak. Myclass my; } * Örnek 2, #include template class Myclass { public: Myclass() { std::cout << "type T is : " << typeid(T).name() << "\n"; std::cout << "type U is : " << typeid(U).name() << "\n"; } }; int main() { // # OUTPUT # // type T is : long // type U is : char Myclass mx; // # OUTPUT # // type T is : int // type U is : double Myclass<> my; Myclass<, long> mz; // SENTAKS HATASI. } * Örnek 3, Bir önceki şablon parametresi de sonraki şablon parametresine varsayılan olarak kullanılabilir. #include template class Myclass { public: Myclass() { std::cout << "type T is : " << typeid(T).name() << "\n"; std::cout << "type U is : " << typeid(U).name() << "\n"; } }; int main() { // # OUTPUT # // type T is : long // type U is : char Myclass mx; // # OUTPUT # // type T is : int // type U is : int Myclass<> my; Myclass<, long> mz; // SENTAKS HATASI. } * Örnek 4, varsayılan şablon argümanı olarak bir başka sınıf şablonları da kullanılabilir. #include template class Neco{}; template> class Myclass { public: Myclass() { std::cout << "type T is : " << typeid(T).name() << "\n"; std::cout << "type U is : " << typeid(U).name() << "\n"; } }; int main() { // # OUTPUT # // type T is : long // type U is : char Myclass mx; // # OUTPUT # // type T is : int // type U is : class Neco Myclass<> my; // # OUTPUT # // type T is : class Neco // type U is : class Neco > Myclass> mz; } >> Fonksiyon şablonlarının ve sınıf şablonlarının varsayılan 'non-type' şablon parametre almaları: * Örnek 1, //.. template class Neco{}; int main() { Neco<10, 20> nx; Neco<50> ny; Neco<> nz; } * Örnek 2, STD deki 'std::array' sınıfının temsili bir implementasyonu. //.. template struct Array{ //.. Interface deki diğer fonksiyonlar. T a[n]; }; >>> Argüman olan ifadenin bir 'Constant Expression' olması gerekmektedir. * Örnek 1, template class Myclass{}; constexpr int square(int x) { return x*x; } int main() { int m = 10; Myclass mx; // Sentaks Hatası. const int cm = 200; Myclass my; // LEGAL. constexpr int cem = 300; Myclass mz; // LEGAL. Myclass ma; // LEGAL. } >>> Argüman olan ifadenin Gerçek Sayı olması sentaks hatasıdır. * Örnek 1, template // SENTAKS HATASI. class Myclass{}; >>> Argüman olan ifadenin 'enum' türünden olması LEGALDİR. * Örnek 1, enum Color{blue}; template class Myclass{}; >>> Argüman olan ifade adres türü de olabilir. Fakat adresi geçilecek nesnenin 'static' ÖMÜRLÜ OLMASI GEREKMEKTEDİR. Buna fonksiyon adresleri de dahildir. * Örnek 1, template class Myclass{}; void func(int); // template template class Neco{}; int main() { int x = 10; Myclass<&x> mx; // SENTAKS HATASI. Neco m; // LEGAL. } >>> C++ 17 itibariyle 'auto' kelimesini de kullanabiliriz. * Örnek 1, template class Myclass{}; int y = 56; int main() { Myclass<12> mx; // '12' rakamını gördüğü için => 'template' şeklinde açılım yapıyor. Myclass<&y> my; // 'auto' yerine gelen tür 'int*' olacaktır. } >>> STL içerisinde bulunan 'type_traits' başlık dosyasının temsili implementasyonu: * Örnek 1, template struct IntegralConstant{ typedef T value_type; auto constexpr value = n; }; using TrueType = IntegralConstant; using FalseType = IntegralConstant; int main() { } >> Şablonların Özelleştirilmesi; 'Template Instantiation', derleyicinin bir şablondan hareketle gerçek kodu yazması olayıdır. İş bu şablondan üretilen ürün ise bir 'Template Instance'. 'template' karşılığı oluşturulan ürün manasına gelen teknik kelime ise 'Template Specialization'. Bu teknik kelime bünyesinde iki şey barındırmaktadır. Bunlar; >>> Derleyicinin, şablona bakarak, gerçek kodu yazdığında ortaya çıkan ürün. >>> Programcı olarak bizlerin belli başlı türlerin kodunu bizzat kendimizin yazması ve derleyiciye bu türler için gerçek kod yazmaması sonucu ortaya çıkan ürün. Bu yaklaşım da kendi içinde iki adet araç barındırmakta; >>>> 'Explicit Specialization' / 'Template Full Specialization' : Hem fonksiyon hem de sınıf şablonlarında kullanılabilir. Aşağıdaki örneği inceleyelim. Normal şablon oluşturur gibi ama sadece 'template<>' yazarak, bu şekilde bir özelleştirme yapabiliriz. 'Function Overload Resolution' A KATILMAZLAR. * Örnek 1, //.. template T Max(T x, T y) { return x < y ? y : x; } int main() { std::cout << Max(12, 56) << "\n"; // Doğru çalışacaktır. const char* p1{"zeynep"}; const char* p2{"aysegul"}; std::cout << Max(p1, p2) << "\n"; // İlgili yazıların tutulduğu adresleri karşılaştıracaktır. // Yazıların kendileri KARŞILAŞTIRILMAYACAKTIR. // Peki bu problemi nasıl çözeriz? El-cevap: aşağıdaki örnekleri inceleyelim. } * Örnek 2, Gerçek bir fonksiyon yazarak: template T Max(T x, T y) { return x < y ? y : x; } const char* Max(const char* p1, const char* p2) { return strcmp(p1, p2) > 0 ? p1 : p2; } int main() { std::cout << Max(12, 56) << "\n"; // Doğru çalışacaktır. const char* p1{"zeynep"}; const char* p2{"aysegul"}; std::cout << Max(p1, p2) << "\n"; // Artık doğru çalışacaktır. // Peki bu gerçek fonksiyonu fonksiyon şablonumuza nasıl gömebiliriz? El-cevap: aşağıdaki // örneği inceleyelim. } * Örnek 3, Yukarıdaki örnekteki gerçek kodu sınıf şablonuna gömülmesi: template // Normal 'Function Template' T Max(T x, T y) { return x < y ? y : x; } template<> // 'Explicit Specialization'. Yukarıdaki şablon olmasaydı, bu kısım sentaks hatası olacaktır. const char* Max(const char* p1, const char* p2) { return strcmp(p1, p2) > 0 ? p1 : p2; } int main() { std::cout << Max(12, 56) << "\n"; // Doğru çalışacaktır. const char* p1{"zeynep"}; const char* p2{"aysegul"}; std::cout << Max(p1, p2) << "\n"; // Artık doğru çalışacaktır. } * Örnek 4, Pekiştirme örneği. #include template void func(T x) { std::cout << "primary template, T is type of " << typeid(T).name() << "\n"; } template<> void func(int x) { std::cout << "func explicit specialization.\n"; } template<> void func(char x) { std::cout << "func explicit specialization.\n"; } int main() { /* # OUTPUT # func explicit specialization. primary template, T is type of double primary template, T is type of float func explicit specialization. */ func(5); func(5.); func(5.f); func('A'); } * Örnek 5, Mülakat Sorusu: //.. template void func(T x) { std::cout << "I\n"; } template void func(int* x) { std::cout << "II\n"; } template void func(T* x) { std::cout << "III\n"; } int main() { int* ptr = nullptr; func(ptr); // 'III' numaralı seçilecektir. Çünkü 'II' numaralı bir 'explicit specialization' dolayısıyla // 'function overload resolution' DAHİL EDİLMİYOR. // 'Partial Ordering Rules' kurallarına göre daha çok deklaratör içeren seçilecektir. İş bu sebepten // ötürü 'III'. // Eğer bir şekilde 'I' seçilmiş olsaydı, bu sefer de 'II' numaralı özelleştirilmiş kullanılacaktı. } * Örnek 6, Mülakat Sorusu //.. template void func(T x) { std::cout << "I\n"; } template void func(T* x) { std::cout << "II\n"; } template void func(int* x) { std::cout << "III\n"; } int main() { int* ptr = nullptr; func(ptr); // 'III' numaralı seçilecektir. Çünkü kendisi 'Function Overload Resolution' ve // 'Partial Ordering Rule' kurallarına göre seçilen 'II' numaralının özelleştirilmiş versiyonu olduğu // için. } * Örnek 7, Sınıf şablonlarında kullanımı: Özelleştirilmiş versiyon ile 'primary' durumdaki version farklı 'interface' sahip olabilir. #include template class Myclass{ public: Myclass() { std::cout << "Myclass primary template.\n"; } void f1(){} }; template<> class Myclass{ public: Myclass() { std::cout << "Myclass explicit specialization.\n"; } void foo1(){} }; int main() { /* # OUTPUT # Myclass primary template. Myclass primary template. Myclass explicit specialization. */ Myclass f1; Myclass d1; Myclass i1; f1.foo(); // Sentaks hatası. Name-look-up hatası. i1.f1(); // Sentaks hatası. Name-look-up hatası. } * Örnek 8, STL deki 'vector' sınıfının 'bool' türden açılımı bir 'specialization'. Dolayısıyla 'int' türden bir açılımdaki 'interface' fonksiyonlarının hepsine sahip değil. Hatta 'bool' açılımında olan ama diğer tür açılımında olmayan fonksiyonlar da vardır. Fakat bunun yerine 'bitset' sınıfını KULLANMALIYIZ. //.. int main() { std::vector vec; vec.flip(); // Specialization a özel bir fonksiyon. vector ivec; ivec.flip(); // Sentaks hatası. Name look-up. } * Örnek 9, 'primary' şablondaki şablon parametresi kadar 'explicit' versiyonunda da parametre olmalı. Her iki versiyondaki şablon parametre adetleri aynı olmalı. >>>> 'Partial Specialization' : Sadece sınıf şablonlarında kullanılabilir. Aşağıdaki örnekleri inceleyelim. * Örnek 1, 'pointer' türler için özelleştirme yapılması. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << typeid(T).name() << ">\n"; } }; template class Myclass{ public: Myclass() { std::cout << "Partial specialization. => Myclass<" << typeid(T*).name() << ">\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass Partial specialization. => Myclass Primary template. => Myclass Partial specialization. => Myclass Partial specialization. => Myclass */ Myclass m1; Myclass m4; Myclass m2; Myclass m3; Myclass m5; // Çıktıdan da görülebildiği üzere 'pointer' tipler için bizim özelleştirdiğimiz version, diğerleri // için asıl şablon kullanılmaktadır. } * Örnek 2, Diziler için özelleştirme yapılması. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << typeid(T).name() << ">\n"; } }; template class Myclass{ public: Myclass() { std::cout << "Partial specialization. => Myclass<" << typeid(T*).name() << ">\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass Partial specialization. => Myclass */ Myclass m1; Myclass m2; // 'm1' ve 'm2' nin FARKLI TÜRLER OLDUĞU unutulmamalıdır. } * Örnek 3, Belirli bir dizi açılımı için de kullanılabilir. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << typeid(T).name() << ">\n"; } }; template class Myclass{ public: Myclass() { std::cout << "Partial specialization. => Myclass<" << typeid(T[31]).name() << ">\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass Primary template. => Myclass Partial specialization. => Myclass */ Myclass m1; Myclass m2; Myclass m3; } * Örnek 4, Referans tipler için de özelleştirme yapılabilir. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << typeid(T).name() << ">\n"; } }; template class Myclass{ public: Myclass() { std::cout << "Partial specialization. => Myclass<" << typeid(T&).name() << ">\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass Primary template. => Myclass Partial specialization. => Myclass */ Myclass m1; Myclass m2; Myclass m3; // 'm1' ve 'm2' nin FARKLI TÜRLER OLDUĞU unutulmamalıdır. } * Örnek 5, birden fazla şablon parametresi için de özelleştirme yapılabilir. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << typeid(T).name() << "," << typeid(U).name() << ">\n"; } }; template class Myclass{ public: Myclass() { std::cout << "Partial specialization. => Myclass<" << typeid(T).name() << "," << typeid(T).name() << ">\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass Primary template. => Myclass Partial specialization. => Myclass */ Myclass m1; Myclass m2; Myclass m3; } * Örnek 6, birden fazla şablon parametresi olan durumlarda 'non-type' parametreler için de özelleştirme yapılabilir. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << typeid(T).name() << "," << typeid(U).name() << ">\n"; } }; template class Myclass{ public: Myclass() { std::cout << "Partial specialization. => Myclass<" << typeid(T).name() << "," << typeid(int).name() << ">\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass Primary template. => Myclass Primary template. => Myclass Partial specialization. => Myclass */ Myclass m1; Myclass m2; Myclass m3; Myclass m4; } * Örnek 7, Asıl şablonda kullanılan şablon parametresi ile özelleştirme için yazılan şablona ait şablon parametrelerinin adetlerinin aynı olmaları BİR ZORUNLULUK DEĞİLDİR. Fakat özelleştirmeyi yazdığımız yer de aynı adet olmak zorundadır. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << typeid(T).name() << ">\n"; } }; template class Myclass>{ public: Myclass() { std::cout << "Partial specialization. => Myclass< std::pair<" << typeid(T).name() << "," << typeid(U).name() << ">>\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass Primary template. => Myclass Primary template. => Myclass Primary template. => Myclass Partial specialization. => Myclass< std::pair> */ Myclass m0; Myclass m1; Myclass m2; Myclass m3; Myclass> m4; // Günün sonunda yine özelleştirilmiş şablon için bir adet argüman bilgisi geçiyoruz fakat dolaylı // yoldan birden fazla tür bilgisi geçmiş oluyoruz. } * Örnek 8, Sadece 'non-type' parametreye sahip asıl şablonlar da özelleştirilebilir. #include template class Myclass{ public: Myclass() { std::cout << "Primary template. => Myclass<" << m << ">\n"; } }; template<> class Myclass<31>{ public: Myclass() { std::cout << "Partial specialization. => Myclass<" << 31 << "\n"; } }; int main() { /* # OUTPUT # Primary template. => Myclass<5> Primary template. => Myclass<8> Partial specialization. => Myclass<31 */ Myclass<5> m1; Myclass<8> m2; Myclass<31> m3; } * Örnek 9, döngü kullanmadan 1-100 arasındaki yazıları ekrana yazdırma: #include // Approach - I struct Neco{ public: Neco(){ static int x{1}; std::cout << x++ << " "; } }; // Approach - II template struct Data : public Data { Data() { std::cout << n << " "; } }; template<> struct Data<0> {}; int main() { /* # OUTPUT # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 ------------------------------------------------------------ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 */ // Approach - I Neco list[100]; std::cout << "\n------------------------------------------------------------\n"; // Approach - II Data<100> mydata; } * Örnek 10, Faktöriyel hesaplanması. // Approach - I constexpr int factorial(int n) { return n < 2 ? 1 : n * factorial(n-1); } // Approach - II template struct Factorial{ static const int value = n * Factorial::value; }; template<> struct Factorial<0>{ static const int value = 1; }; int main() { // Approach - I constexpr auto val = factorial(5); // Gerek fonksiyonun bir 'constexpr' fonksiyon olması gerek geçilen argümanın bir 'constant' // olmasından dolayı bu fonksiyonun değeri derleme zamanında hesaplanmıştır. // Approach - II const auto valTwo = Factorial<5>::value; } >> 'Alias Template' : 'typedef' ve 'using' eş isim bildirimlerinin şablonlar için olan versiyonu. * Örnek 1, typedef int Word; //using Word = int; typedef int(*fPtr)(int); // using fptr = int(*)(int); using intArray10 = int[10]; template // 'primary template' class Myclass{}; template // 'Alias Template' using MyclassInt = Myclass; int main() { intArray10 myArray; intArray10 myArrayTwo[20]; //int myArrayTwo[20][10] Myclass m1; // Normal yollar ile nesne oluşturma MyclassInt m2; // => Myclass m2; } * Örnek 2, //.. template using greaterSet = std::set>; int main() { greaterSet mySet; // => std::set> mySet; } * Örnek 3, //.. template using myCustomPtr = T*; int main() { int x = 200; myCustomPtr ptr = &x; // => int* ptr = &x; } * Örnek 4, //.. template using greaterMap = std::map>; int main() { std::map> m1; // => greaterMap m1; } > 'Compile-Time-IF' deyimi: C++17 ile dile eklenmiştir. 'if' anahtar sözcüğünden hemen sonra 'constexpr' anahtar sözcüğünü yazıyoruz. Dolayısıyla argüman olan ifadenin bir 'sabit-ifadesi' olması gerekmektedir. * Örnek 1, //.. template void func(T x) { if constexpr ( std::is_integral_v ) { std::cout << "Tam sayilar için kullanilacak kod bloğu...\n"; } else { std::cout << "Diğer sayilar için kullanilacak kod bloğu...\n"; } } int main() { /* # OUTPUT # Tam sayilar için kullanilacak kod bloğu... Diğer sayilar için kullanilacak kod bloğu... */ func(12); func(1.2f); return 0; } * Örnek 2, //.. template auto getValue(T t) { if constexpr ( std::is_pointer_v ) return *t; // Parantez içerisindeki ifade 'true' döndürürse bu kısım derleme aşamasına katılacak alınacak. else return t; // Parantez içerisindeki ifade 'false' döndürürse bu kısım derleme aşamasına KATILMAYACAK. } int main() { /* # OUTPUT # 87 87 4.5 4.5 */ int ival{ 87 }; int* iPtr{ &ival }; double dval{ 4.5 }; double* dPtr{ &dval }; std::cout << getValue(ival) << "\n"; std::cout << getValue(iPtr) << "\n"; std::cout << getValue(dval) << "\n"; std::cout << getValue(dPtr) << "\n"; return 0; } * Örnek 3, //.. template std::string as_string(T x) { // 'x' eğer 'std::string' türünden ise aşağıdaki deyimler derlemeye katılmayacak. if constexpr ( std::is_same_v ) return x; // 'x' eğer aritmatik türden ise aşağıdaki ve yukarıdaki deyimler derlemeye katılmayacak. else if constexpr ( std::is_arithmetic_v ) return std::to_string(x); // Eğer 'x' yukarıdaki şartlara haiz değil, başka bir türden ise sadece aşağıdaki deyim derlemeye katılacaktır. else return std::string{x}; } int main() { /* # OUTPUT # 42 4.200000 Hello World */ std::cout << as_string(42) << "\n"; std::cout << as_string(4.2) << "\n"; std::cout << as_string(std::string{"Hello"}) << "\n"; std::cout << as_string("World") << "\n"; return 0; } * Örnek 4, //.. template void func(T& tx) { if( tx > 0 ) // Bu 'if' bloğu her halükarda derlemeye katılacaktır. { // Eğer 'true' değer döndürülürse, bu 'if' bloğu derlemeye katılacaktır. if constexpr ( std::is_integral_v ) ++tx; else // Eğer 'false' değer döndürülse bu 'else' bloğu derlemeye katılacaktır. --tx; } } int main() { /* # OUTPUT # ival : 6 dval : 4.5 */ int ival{ 5 }; func(ival); double dval{ 5.5 }; func(dval); std::cout << "ival : " << ival << "\n"; std::cout << "dval : " << dval << "\n"; return 0; } * Örnek 5, HER ŞEYİN BAŞI İSİM ARAMADIR. //.. template void func(T x) { if constexpr ( true ) { ++x; } else // there are no arguments to ‘foo’ that depend on a template parameter, so a declaration of // ‘foo’ must be available [-fpermissive] foo(); } int main() { //.. return 0; } * Örnek 6, //.. constexpr auto func() { if constexpr ( sizeof(int) > 4u ) return 1; else return 0.5; } int main() { /* # OUTPUT # Value : 0.5 */ constexpr auto value { func() }; std::cout << "Value : " << value << "\n"; return 0; } * Örnek 7, GÜNÜN EN GÜZEL ÖRNEĞİ //.. // Using 'if-constexpr' template void advanceTwo(Iter& pos, Dist n) { using Cat = typename std::iterator_traits::iterator_category; if constexpr ( std::is_same_v ) { pos += n; } else if constexpr ( std::is_same_v ) { if(n > 0) { while(n--) ++pos; } else { while(n++) --pos; } } else if constexpr ( std::is_same_v ) { while(n--) ++pos; } else { std::cerr << "UNKNOWN IF-CLAUSE BLOCK\n"; } } // Tag-dispatch yaklaşımı: template void advanceOne(Iter& pos, Dist n) { using Cat = typename std::iterator_traits::iterator_category; advance_impl(pos, n, Cat{}); } // Implementation for random access iterators: template void advance_impl(Iter& pos, Dist n, std::random_access_iterator_tag) { pos += n; } // Implementation for bidirectional access iterators: template void advance_impl(Iter& pos, Dist n, std::bidirectional_access_iterator_tag) { if(n > 0) { while(n--) ++pos; } else { while(n++) --pos; } } // Implementation for input iterators: template void advance_impl(Iter& pos, Dist n, std::input_iterator_tag) { while(n--) ++pos; } int main() { /* # OUTPUT # */ //.. return 0; } * Örnek 8, Şablon haricinde kullanması durumunda bütün bloklar derlemeye alınır. //.. struct Neco{ }; int main() { /* # OUTPUT # */ Neco nec; if constexpr ( sizeof(int) > 1u ) ++nec; // error: no match for ‘operator++’ (operand type is ‘Neco’) else --nec; // error: no match for ‘operator--’ (operand type is ‘Neco’) return 0; } * Örnek 9, //.. struct Nec{ Nec( int i, double d, std::string s ) : ival{i}, dval{d}, str{s} {} int ival{}; double dval{}; std::string str{}; }; // 'forward-declaration' for 'primary-template' template auto& get(Nec& nec); // 'specialization' for the 'primary-template' template<> auto& get<0>(Nec& nec){ return nec.ival; } template<> auto& get<1>(Nec& nec){ return nec.dval; } template<> auto& get<2>(Nec& nec){ return nec.str; } // 'constexpr-if' kullanırsak template auto& Get(Nec& nec) { if constexpr ( n == 0 ) return nec.ival; else if constexpr ( n == 1 ) return nec.dval; else if constexpr (n == 2) return nec.str; else return "SOME UNKNOWN ERROR OCCURED!"; } Nec foo() { return { 12, 4.5, "HelloWorld" }; } int main() { /* # OUTPUT # 12 / 4.5 / HelloWorld 12 / 4.5 / HelloWorld 12 / 4.5 / HelloWorld */ auto [ i, d, name] = foo(); std::cout << i << " / " << d << " / " << name << "\n"; Nec nec{ foo() }; std::cout << get<0>(nec) << " / " << get<1>(nec) << " / " << get<2>(nec) << "\n"; std::cout << Get<0>(nec) << " / " << Get<1>(nec) << " / " << Get<2>(nec) << "\n"; return 0; } * Örnek 10, //.. // 'primary-template' template constexpr int fibonacci() { return fibonacci() + fibonacci(); } // '1' için özelleştirme template<> constexpr int fibonacci<1>() { return 1; } // '0' için özelleştirme template<> constexpr int fibonacci<0>() { return 0; } // 'constexpr-if' kullanarak template constexpr int fibo() { if constexpr ( N >= 2 ) return fibo() + fibo(); else return N; } int main() { /* # OUTPUT # 610 / 5 */ // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610 // x // y constexpr auto x{ fibonacci<15>() }; constexpr auto y{ fibo<5>() }; std::cout << x << " / " << y << "\n"; return 0; } * Örnek 11, //.. template void func(T x) { // BURADA ARTIK KISA DEVRE DAVRANIŞI SÖZ KONUSU DEĞİLDİR. EĞER 'Run-Time-If' kullansaydık, KISA DEVRE oluşacaktı. if constexpr ( std::is_integral::value && std::numeric_limits::min() < 10) { // error: no match for ‘operator<’ (operand types are ‘std::__cxx11::basic_string’ and ‘int’) } } int main() { /* # OUTPUT # */ func(12); func(std::string{}); return 0; }