> C++ dilindeki şablonlar ve genel hatırlatmalar: Anımsanacağı üzere C++ dilindeki şablonlar şunlardır; "Function Template", "Class Template", "Variable Template", "Alias Template" ve "concept" Ve bunları açtığımız zaman, yani bunların "specialization" halleri ise sırasıyla, "Function", "Class", "Variable", "Tür Eş İsmi" ve "Concept" oluşur. Hatırlarsanız "concept" kelimesi hem şablonun kendisi hem de o şablon açıldığında ortaya çıkan ürüne denmektedir. Diğer yandan şablonların parametreleri de şunlar olabilir; "Type Parameter", "Non-Type Parameter" ve "Template Parameter". Şimdi de şablonlarla alakalı şu alt başlıkları irdeleyelim: >> Şablonlardan bahsederken şu konular da akla gelmektedir; Deduction Specialization Overloading CTAD Full(Explicit) Specialization Partial Overloading Rules Deduction Guides Partial Specialization Friend Declarations Meta Functions (type_traits) SFINAE Tag Dispatch Default Template Arguments Abbreviated Template Syntax Constrained Templates CRTP Member Templates Expression Templates declval type_identity Perfect Forwarding static if static assert Fold Expressions >> Şablonlara ilişkin terminoloji ise şu şekildedir; =================================================== template< typename T > class Heap; --------- ---- | a: | b: a: "Template-Parameter-List" b: "Template-Name" =================================================== template< typename T > class Heap; template< typename T > class Heap; -------- | a: template<> // "<>" indicates that it is an excplicit specialization, or diamon specialization. class Heap ; ---- ----- | b: | c: a: "Template-ID" b: "Template-Name" c: "Template-Argument-List" =================================================== Heap< int > aHeap; --- | a: Heap aHeap2; ----------- | b: a: "Template-Argument-List" b: "Template-ID" =================================================== template< typename T > void print (const T& x) {} ---------- ----- | a: | b: a: "Template-Parameter-List" b: "Template-Name" =================================================== Bir şablonun belli bir türe göre açıldığında ortaya çıkan ürüne ise "Specialization" denmektedir. Her ne kadar "Specialization" kelimesi "Full(Explicit) Specialization" / "Partial Specialization" kavramlarını akla getirsede, buradaki kullanım daha geneldir. >> Şablonlarda, "T" için yapılacak tür çıkarımı ise şu kurallara bağlıdır; template void foo(T p); /* * > Fonksiyon isimleri ve dizi isimleri söz konusu olduğunda "decay" gerçekleşir. * > "const" özelliği düşer. * > "reference" özelliği düşer. * > Çıkarım sonucunda "T" ile "p" nin türleri aynıdır. */ template void foo(T& p); /* * > Fonksiyon isimleri ve dizi isimleri söz konusu olduğunda "decay" GERÇEKLEŞMEZ. * > "const" özelliği DÜŞMEZ. * > "reference" özelliği DÜŞMEZ. * > Çıkarım sonucunda "T" ile "p" nin türleri FARKLI OLABİLİR. */ template void foo(T&&); Aşağıda bu konuya ilişkin örnekler verilmiştir: * Örnek 1, #include #include #include /* * "func" ın üçüncü parametresi aslında bir "Template Parameter". İki tane * "type" parametresine sahip bir "template" i argüman olarak geçmeliyiz. */ templatetypename Con> void func(const Con& con) { std::cout << "T is " << typeid(T).name() << '\n'; std::cout << "A is " << typeid(A).name() << '\n'; std::cout << "Con is " << typeid(Con).name() << '\n'; std::cout << "con is " << typeid(con).name() << '\n'; } int main() { /* # OUTPUT # T is int A is class std::allocator Con is class std::vector con is class std::vector > ======================== T is double A is class std::allocator Con is class std::list con is class std::list > */ std::vector ivec; std::list ilist; func(ivec); std::cout << "\n========================\n\n"; func(ilist); } * Örnek 2, #include #include #include template void func(T) { std::cout << "void func \n<" << typeid(T).name() << "> (\n" << typeid(T).name() << "\n)"; } int main() { /* # OUTPUT # void func ( void (__cdecl*)(double) ) */ void (*func_ptr)(double) = &func; func(func_ptr); } * Örnek 3, #include #include template void foo( std::array arr1, std::array arr2 ) { std::cout << "arr1<" << typeid(T).name() << ',' << sizeof(U) << ">\n"; std::cout << "arr2<" << typeid(U).name() << ',' << sizeof(T) << ">\n"; } int main() { /* # OUTPUT # arr1 arr2 */ std::array a{}; std::array b{}; foo(a, b); std::cout << "\n==========================\n\n"; std::array c; // 'void foo(std::array,std::array)' // : cannot convert argument 2 from 'std::array' to 'std::array' // "a" için yapılan tür çıkarımı sonucunda "T" için "int", "U" için "double" // geleceğinden "int<8>" türünde bir dizi olacak. // Fakat "c" için yapıldığında "U" için "double", "T" için "int" geleceğinden // "double<4>" olacak ama argüman olan dizimiz "" olduğundan sentaks // hatası alacağız. // foo(a, c); } * Örnek 4, template void f1(T*); template void f2(E(&)[N]); template void f3(T1(T2::*)(T3*)); class s { public: void f(double*); }; void g(int*** ppp) { bool b[42]; f2(b); // "E" is "bool" // "N" is "42" f1(ppp); // "T" is "int**" f3(&s::f); // "T1" is "void" // "T2" is "s" // "T3" is "double" } int main() { } * Örnek 5, template void func(T(*)(U)); int foo(double); int main() { func(foo); // "T" is "int" // "U" is "double" // func([](int i) { return i + 5; }); // Error: No Implicit Convertion // Positive Lambda Expression func(+[](int i) { return i + 5; }); // "T" is "int" // "U" is "int" } * Örnek 6, #include template void func(T&& x, const std::vector& ivec); int main() { std::vector ivec(10); // func(ivec[0], ivec); // "ivec[0]" ifadesinin türü "int" ve değer // kategorisi "L-Value". Dolayısıyla "T" için // "int&" türü olacak. // "ivec" ifadesi yine aynı şekilde fakat tür // çıkarımı "T" için "int" olacak. "ambiguous" // oluşacağından sentaks hatası. func((int)ivec[0], ivec); // OK // Birinci parametrenin türü "int", değer // kategorisi ise "R-Value". Dolayısıyla "T" // için "int" olacak. // İkinci parametre için "T" yine "int" olacak. } * Örnek 7, // Burada fonksiyon parametresi ile bir işimiz olmadığından, "T=0" şeklinde yazdık. Aksi halde "T x=0" biçiminde // yazmalıyız. template void func(T = 0); class Myclass {}; int main() { int x{ 20 }; func(&x); // "T" is "int*" func(20); // "T" is "int" func(2.0); // "T" is "double" func(Myclass{}); // "T" is "Myclass" //func(); // Error } >> Anımsanacağı üzere şablon parametreleri üç farklı şekildedir. Bunlar, "Type Template Parameter", "Non-Type Template Parameter" ve "Template Template Parameter". şekilleridir. Şimdi de bu şekilleri sırayla inceleyelim: >>> "Type Template Parameter" : template template template template void func(T x); void func(T& x); void func(const T& x); void func(T&& x); void foo(auto x); void foo(auto& x); void foo(const auto& x); void foo(auto&& x); Eğer "concept" kullanmak istersek de; template void func(T x); void func(std::integral auto x); >>> "Non-Type Template Parameter" : "NTTP" diye de kısaltılır. Buradaki tür bilgisi tam sayı olabilir, gösterici ve referans türlerinden olabilir ki bunlara sınıfların üye fonksiyon ve global fonksiyon göstericileri ve referansları da dahildir, gerçek sayı türleri olabilir. * Örnek 1, class Myclass { public: double foo(double); }; int g{}; int foo(int); // template template class A {}; template class B {}; template // Function Pointer class C {}; template // Member Function Pointer class D {}; template class E {}; int main() { int ival{}; // OK A<5> ax; // -----(1): "5" nasılki bir tür belirtmiyorsa, sabit olarak işlev görüyorsa // OK B<&g> bx; // -----(2): "&g" ifadesi de aynı işlevi görecektir. // error C2971: 'B': template parameter 'x': 'l': // a variable with non-static storage duration cannot be used as a non-type argument // int l{}; // B<&l> by; // OK static int q{}; B<&q> bz; // -----(3): "&q" ifadesi de aynı işlevi görecektir. // OK C cx; // -----(4): "foo" ifadesi de aynı işlevi görecektir. // OK D<&Myclass::foo> dx; // -----(5): "&Myclass::foo" ifadesi de aynı işlevi görecektir. // OK E ex; // -----(6): "&g" ifadesi de aynı işlevi görecektir. } * Örnek 2, #include template class Neco {}; char g_s[] = "ULYA"; const char g_cs[] = "YURUK"; int main() { Neco n1; // OK Neco n2; // OK static char s2[] = "yuruk"; Neco n3; // OK // Error: "s1" has an auto life time. char s1[] = "ulya"; Neco n4; // Error: "String Literals" belongs to the "Internal Linkage". // So, we cannot use them with this way. Neco<"UlyA"> n5; } * Örnek 3, #include template // "non-type" parametreleri kullanmadığımız için isim vermedik. class Nec {}; int main() { Nec<10, true> n1; // OK Nec n2; // OK Nec<5, sizeof(int) > 2> n3; // ">" aslında açısal parantezin kapananı olarak ele alınıyor. Nec<5, (sizeof(int) > 2)> n4; // OK } * Örnek 4, #include template constexpr bool less(const T(&a)[N], const T(&b)[M]) { for (int i = 0; i < N && i < M; ++i) { if (a[i] < b[i]) return true; if (b[i] < a[i]) return false; } return N < M; } int main() { int a[] = { 3, 7, 9 }; constexpr int b[] = { 3, 7, 9, 2, 6 }; std::cout << std::boolalpha << less(a, b) << '\n'; constexpr int c[] = { 3, 7, 9, 2, 6 }; constexpr auto f = less(b, c); std::cout << std::boolalpha << f << '\n'; } * Örnek 5, #include // C++20 template T foo(); int main() { auto x = foo<5>(); // "Val" is "5", "T" is "int". auto y = foo<5.5>(); // "Val" is "5.5", "T" is "double". auto z = foo<3, double>(); // "Val" is "3", "T" is "double". } * Örnek 6, struct Neco { int foo(int); int bar(int); int mx; int my; }; template class C {}; int main() { C<&Neco::mx, &Neco::foo> c1; C<&Neco::my, &Neco::foo> c2; C<&Neco::mx, &Neco::bar> c3; C<&Neco::my, &Neco::bar> c4; } * Örnek 7, #include #include template T add_value(T x) { return x + val; } int main() { std::vector source(100); std::vector dest(100); transform(begin(source), end(source), begin(dest), add_value); transform(begin(source), end(source), begin(dest), [](auto i) { return i + 10; }); } * Örnek 8, #include template // non-type parameter class Myclass { public: Myclass() { ++x; } }; int g{30}; int main() { std::cout << g << '\n'; // 30 Myclass<(g)> m1; std::cout << g << '\n'; // 31 } * Örnek 9, template void bar() { // ++N; // error C2105: '++' needs l-value ++r; // auto p1 = &N; // error C2101: '&' on constant auto p2 = &r; // int& r1 = N; // error C2440: 'initializing': cannot convert from 'int' to 'int &' int& r2 = r; } int g{31}; int main() { bar<10, g>(); } * Örnek 10, #include template struct Sum { static auto constexpr value = x + y; }; int main() { std::cout << Sum<3.5, 35L>::value << '\n'; // 38.5 } * Örnek 11.0, C++20 ile birlikte "non-type" parametreler gerçek sayılardan olabilir. #include template struct Neco { Neco() { std::cout << d << '\n'; } }; constexpr auto foo(double d) { return d + d; } int main() { constexpr auto dval{ 19.93 }; Neco d1; // 19.93 } * Örnek 11.1, #include #include template struct Neco; int main() { static_assert(std::is_same_v< Neco<0.3>, Neco<0.7 - 0.4> >); // FAILS static_assert(std::is_same_v< Neco<0.2>, Neco<0.1 + 0.1> >); // HOLDS static_assert(std::is_same_v< Neco<+0.>, Neco<-0.> >); // FAILS } * Örnek 11.2, #include #include template struct StepOne {}; template // All must be type "double". struct StepTwo {}; template // All can be different types. struct StepThree {}; int main() { StepOne<1.2f> so1; StepOne<1.2> so2; StepOne<1.2L> so3; StepTwo<0.1, 1.2, 3.4> st1; StepTwo<5.6, 7.8, 9.1> st2; StepThree<0.1f, 0.2, 0.3L, 'A', 10, 20LL> sth1; } * Örnek 12.0, C++20 ile birlikte sınıflar da belli şartları sağladıkları müddetçe "non-type" olabilirler. Bu şartlardan birisi de ilgili sınıfın kurucu işlevinin "constexpr" olmasıdır. Bu şartlardan bir diğeri de "private" elemana SAHİP OLMAMASIDIR. #include #include template struct MyStringLiteral { constexpr MyStringLiteral(const char(&arr)[N]) { std::copy(arr, arr + N, s); } char s[N]; }; template struct Neco { Neco() { std::cout << str.s << '\n'; } }; int main() { // OK : UlyaYuruk // Arka planda "MyStringLiteral" sınıfının kurucu işlevine // çağrı yapılmıştır. Neco<"UlyaYuruk"> mynec; } * Örnek 12.1, #include struct TNullOpt { }; /* "OptionalInt" is a Literal Class Type */ struct OptionalInt { constexpr OptionalInt(TNullOpt) {} constexpr OptionalInt(int value) : has_value{ true }, value(value) {} const bool has_value{ false }; const uint32_t value{}; }; template void Print() { if constexpr (maybe.has_value) std::cout << "Value: " << maybe.value << '\n'; else std::cout << "No Value\n"; } int main() { // Way - I constexpr OptionalInt x(123); Print(); // Value: 123 // Way - II Print(); // Value: 312 // Way - III Print<231>(); // Value: 231 // Way - I TNullOpt NullOpt; constexpr OptionalInt y(NullOpt); // No Value Print(); // Way - II Print(); // No Value // Way - III Print(); // No Value } * Örnek 12.2, #include template struct Myclass { Myclass() { std::cout << typeid(decltype(x)).name() << '\n'; } }; int main() { // class `int __cdecl main(void)'::`2':: Myclass < []() {} > mx; // class `int __cdecl main(void)'::`2':: Myclass < [](int x) { std::cout << "x: " << x; } > my; // class `int __cdecl main(void)'::`2':: Myclass < [](double x) { return x * x; } > mz; } * Örnek 12.3, #include template struct Myclass { Myclass() { std::cout << typeid(decltype(x)).name() << '\n'; } auto Print(auto value) const { std::cout << "Value: [" << x(value) << "]\n"; } }; int main() { // class `int __cdecl main(void)'::`2':: // Value: [100] Myclass < [](int x) { return x * x; } > my; my.Print(10); // class `int __cdecl main(void)'::`2':: // Value: [149.084] Myclass < [](double x) { return x * x; } > mz; mz.Print(12.21); } * Örnek 12.4, #include #include #include template struct Neco { }; int main() { // Way - I constexpr std::pair sp1{ 13, 1.3 }; Neco nec1; // Way - II constexpr std::pair sp2{ 26, 2.6 }; Neco nec2; // Way - III Neco < std::pair{ 52, 5.2 } > nec3; // Way - I constexpr std::array primes1{ 2, 3, 5, 7 }; Neco nec4; // Way - II constexpr std::array primes2{ 11, 13, 17, 19 }; Neco nec5; // Way - III Neco < std::array{ 2, 3, 5, 7, 11, 13, 17, 19 } > nec6; } * Örnek 12.5, #include #include constexpr int foo() { return 42; } struct Lit { int x = foo(); int y; constexpr Lit(int i) : y(i) {} }; struct Data { int i; std::array vals; Lit lit; }; template void func() { std::cout << typeid(Obj).name() << '\n'; } int main() { func < Data{ 42, { 1, 2, 3}, 42} > (); // struct Data constexpr Data d2{ 1, {2}, 3 }; func(); // struct Data } * Örnek 12.6, #include #include struct Vat { double Val; constexpr Vat(double v) : Val(v) {} friend std::ostream& operator<<(std::ostream& os, const Vat& other) { return os << other.Val; } }; template int add_vat(int value) { return static_cast(std::round(value * (1 + vat.Val))); } int main() { constexpr Vat v{ .2 }; std::cout << "v: " << v << '\n'; // v: 0.2 std::cout << add_vat(45) << '\n'; // 54 std::cout << add_vat(317) << '\n'; // 380 std::cout << add_vat < Vat{ 48.17 } > (4817) << '\n'; // 236852 } >>> "Template Template Parameter" : * Örnek 1, Aşağıdaki örnekte "Template Template Parameter", iki tane "Type Template Parameter" içermelidir. #include template struct NE{}; template struct Neco{}; template struct Erg{}; template< typename T, // Type Parameter typename U, // Type Parameter templatetypename Con // Template Parameter > class NecoErg{}; int main() { NecoErg m1; NecoErg m2; // Error: type/value mismatch at argument 1 in template parameter list for // ‘template class std::vector’ // NecoErg> m3; // Error: type/value mismatch at argument 3 in template parameter list for // ‘template class Con> class NecoErg’ // NecoErg m4; } * Örnek 2.0, #include #include templatetypename C> void func(const C& c) { std::cout << "Size: " << c.size() << '\n'; } int main() { /* # OUTPUT # Size: 50 */ std::vector ivec(50); // "T" is "int". // "C" is "std::vector" // "c" is "std::vector" func(ivec); } * Örnek 2.1, #include #include templatetypename C> void func(const C& c) { std::cout << "Size: " << c.size() << '\n'; } int main() { /* # OUTPUT # Size: 500 */ std::vector ivec(500); func(ivec); } * Örnek 3, #include #include #include templatetypename Con> void func(const Con& con) { std::cout << "T is a " << typeid(T).name() << '\n'; std::cout << "A is a " << typeid(A).name() << '\n'; std::cout << "Con is a " << typeid(Con).name() << '\n'; } int main() { std::vector ivec; std::list dlist; // "T" is "int" // "A" is "class std::allocator" // "Con" is "class std::vector" func(ivec); // "T" is "double" // "A" is "class std::allocator" // "Con" is "class std::list" func(dlist); } * Örnek 4.0, #include #include template class Nec {}; templatetypename Temp> class Myclass { public: Myclass() { Temp x; Temp y; std::cout << typeid(Myclass).name() << '\n'; std::cout << typeid(Temp).name() << '\n'; std::cout << typeid(x).name() << '\n'; std::cout << typeid(y).name() << '\n'; } }; int main() { /* # OUTPUT # class Myclass class Nec class Nec class Nec */ Myclass x; static_assert(std::is_same_v>); } * Örnek 4.1, #include #include template class Nec {}; templatetypename Temp> class Myclass { public: Myclass() { Temp<5> x; Temp<10> y; std::cout << typeid(Myclass).name() << '\n'; std::cout << typeid(Temp).name() << '\n'; std::cout << typeid(x).name() << '\n'; std::cout << typeid(y).name() << '\n'; } }; int main() { Myclass x; static_assert(std::is_same_v>); } * Örnek 4.2, #include #include template class Nec {}; templatetypename Temp> class Myclass { public: Myclass() { Temp<5> x; Temp<10> y; Temp<105u> z; Temp<10.5f> q; std::cout << typeid(Myclass).name() << '\n'; std::cout << typeid(Temp).name() << '\n'; std::cout << typeid(x).name() << '\n'; std::cout << typeid(y).name() << '\n'; } }; int main() { Myclass x; static_assert(std::is_same_v>); } * Örnek 5, #include #include template class A{}; template class B{}; template class C{}; // "Template Template Parameter Pack" // template< templatetypename T, templatetypename U, templatetypename V> template< templatetypename ...Ts> class Myclass { public: Myclass() { // "class Myclass< class A, class B, class C>" std::cout << typeid(Myclass).name() << '\n'; // "class std::tuple< class A, class B, class C>" std::cout << typeid(std::tuple...>).name() << '\n'; // "class std::tuple< class A, class B, class C>" std::cout << typeid(std::tuple...>).name() << '\n'; } }; int main() { Myclass x; } * Örnek 6, #include #include #include templatetypename C> std::ostream& operator<<(std::ostream& os, const C& c) { for (const auto& e : c) os << e << ' '; return os; } int main() { std::vector ivec{10, 20, 30, 40, 50, 60}; std::cout << ivec << '\n'; // 10 20 30 40 50 60 std::list ilist{1, 2, 3, 4, 5, 6}; std::cout << ilist << '\n'; // 1 2 3 4 5 6 } * Örnek 7.0, #include #include #include template< templatetypename Con > void f1() {} template< templatetypename Con > void f2() {} template< templatetypename Con > void f3() {} template< templatetypename Con > void f4() {} template class T1{}; template class T2{}; template class T3{}; template class T4{}; int main() { f1(); f2(); f3(); f4(); f4(); f4(); f4(); } * Örnek 7.1, #include #include #include #include template std::ostream& operator<<(std::ostream& os, const std::pair& p) { return os << "<" << p.first << "," << p.second << ">"; } template< templatetypename Con, typename ...Ts> std::ostream& operator<<(std::ostream& os, const Con& c) { for (const auto& i : c) os << i << ' '; return os; } int main() { /* # OUTPUT # */ std::map mymap{ {2,4}, {4,7}, {3,5} }; std::cout << mymap << '\n'; // <2,4> <3,5> <4,7> std::vector ivec{10, 20, 30, 40, 50, 60}; std::cout << ivec << '\n'; // 10 20 30 40 50 60 std::list ilist{1, 2, 3, 4, 5, 6}; std::cout << ilist << '\n'; // 1 2 3 4 5 6 } >> Şimdi de "Variable Templates" konusunu ele alalım. Nasılki sınıf şablonlarının açılımları sonucunda sınıf, fonksiyon şablonlarının açılımında bir fonksiyon elde ediyoruz, değişken şablonlarının açılımından da bir değişken elde ederiz. C++14 ile dile eklenmiştir. * Örnek 1, #include template constexpr bool is_four = sizeof(T) == 4; template bool is_eight = sizeof(T) == 8; int main() { // OK since "is_four" is "constexpr". if constexpr (is_four) { // sizeof int has 4 bytes std::cout << "sizeof int has " << sizeof(int) << " bytes\n"; } // OK. if (is_four) { // sizeof double has 8 bytes std::cout << "sizeof double has " << sizeof(double) << " bytes\n"; } // OK since "is_eight" is NOT "constexpr". if (is_eight) { // sizeof double has 8 bytes std::cout << "sizeof double has " << sizeof(double) << " bytes\n"; } } * Örnek 2, #include #include // "T" bir "constexpr" olarak nitelendiği // için "Compile-Time" sabiti olarak da // kullanabiliriz. template constexpr T pi = T(3.1415926535897932385L); template T circular_area(T r) { return pi *r * r; } int main() { std::cout << pi << '\n'; // 3 std::cout << pi << '\n'; // 3.14159 std::cout << pi << '\n'; // 3.14159 std::cout << pi << '\n'; // 3.14159 std::cout << circular_area(123456789) << '\n'; // 45724736250571563 } * Örnek 3, #include template constexpr std::size_t fact = n * fact; // Base Case: (Explicit Specialization) template<> constexpr std::size_t fact<0> = 1u; int main() { // "val" is "120U". // It is calculated in the compile time. constexpr auto val = fact<5>; static_assert(val == 120U); // HOLDS } * Örnek 4, #include template constexpr std::size_t power = base * power; // Partial Specialization template constexpr std::size_t power = base; // Partial Specialization template constexpr std::size_t power = 1; int main() { static_assert(power<3, 4> == 81u); // HOLDS static_assert(power<2, 10> == 1024u); // HOLDS } * Örnek 5, #include template constexpr int ar[] = { Vals... }; int main() { // 1 2 3 4 5 for (auto i : ar< 1, 2, 3, 4, 5>) std::cout << i << ' '; } * Örnek 6, #include #include template constexpr T sz = static_cast(sizeof(U)); class Myclass {}; int main() { // "var" is of type "int" and // has value of "1". constexpr auto var = sz; } * Örnek 7, #include template constexpr bool IsPositive{ N > 0 }; class BigInt { public: constexpr BigInt(int x) : mx(x) {} constexpr bool operator>(const BigInt& other) const{ return mx > other.mx; } int mx; }; int main() { // Positive if constexpr (IsPositive < BigInt{ 98 } > ) { std::cout << "Positive\n"; } else { std::cout << "Negative\n"; } } * Örnek 8, #include template constexpr int sum = (... + Vals); // Fold Expression template constexpr int sum_square = (... + (Vals * Vals)); int main() { constexpr auto result_sum = sum<1, 2, 3, 4, 5, 6, 7, 8, 9>; static_assert(result_sum == 45); // HOLDS constexpr auto result_sum_square = sum_square< 1, 2, 3, 4, 5, 6, 7, 8, 9>; static_assert(result_sum_square == 285); // HOLDS } * Örnek 9, #include template constexpr int div_left = (... / Vals); template constexpr int div_right = (Vals / ...); int main() { { // Result: 5 constexpr auto result = (((500 / 5) / 10) / 2); constexpr auto result_1 = div_left<500, 5, 10, 2>; static_assert(result == result_1); } { // Result: 500 constexpr auto result = (500 / (5 / (10 / 2))); constexpr auto result_1 = div_right<500, 5, 10, 2>; static_assert(result == result_1); } } >> Şimdi de şablon parametrelerinin varsayılan argüman almasına değinelim; Burada fonksiyonların parametre değişkenlerinin varsayılan argüman almasıyla şablon parametrelerinin varsayılan argüman alması birbiriyle karıştırılabilmektedir. Diğer yandan sınıf şablonları ile fonksiyon şablonları arasında da varsayılan argüman alma konusunda bir kural farklılığı vardır. Şöyleki; sınıf şablonlarının sadece en sondaki şablon parametresi varsayılan argüman alabilirken, fonksiyon şablonlarında böyle bir şey kısıtlama yoktur. Fakat bunun da şöyle bir "work-around" yöntemi vardır; anımsanacağı üzere fonksiyon bildirimlerini derleyiciler kümülatif olarak ele alırlar. İşte bunu sınıf şablonlarında da kullanabiliriz. * Örnek 1, // Fonksiyon Şablonları için OK template void func() { //... } // Sınıf Şablonları için Sentaks Hatası template class Myclass{ }; * Örnek 2, #include template struct DefaultDeleter{ void operator()(T* p) { std::cout << "DefaultDeleter::operator was called\n"; delete p; } }; template> class UniquePtr{ public: UniquePtr() : mp{ new T } {} ~UniquePtr() { if (mp) D{}(mp); } private: T* mp; }; int main() { /* # OUTPUT # main basladi DefaultDeleter::operator was called main bitecek */ std::cout << "main basladi\n"; { UniquePtr up1; } std::cout << "main bitecek\n"; } * Örnek 3, #include #include template class A{ }; template> class B{ }; int main() { A a1; // A A a2; // A B b1; // B> B> b2; // B> } * Örnek 4, Yukarıda bahsedilen "Work-Around". Tabii sınıf şablonlarında, şablon parametresini kullanmadığımız için, "T, U, V" harflerini silsek de olurdu. #include // Derleyici aşağıdaki bildirimleri kümülatif olarak ele alacağından, // günün sonunda her üç argüman da aslında varsayılan argüman // alacaktır. İşte bunu sınıf şablonlarında da uygulayabiliriz ----(1) void f1(int, int, int = 0); void f1(int, int = 1, int); void f1(int = 2, int, int) {} // ----(1) template class Myclass; template class Myclass; template class Myclass{}; int main() { /* # OUTPUT # */ Myclass<> m; static_assert( std::same_as< decltype(m), Myclass > ); // HOLDS } * Örnek 5, #include template struct NE{}; template struct Neco{}; template struct Erg{}; template< typename T, // Type Parameter typename U, // Type Parameter templatetypename Con = Neco // Template Parameter > class NecoErg{}; int main() { NecoErg m1; // NecoErg m1; NecoErg m2; // Error: type/value mismatch at argument 1 in template parameter list for // ‘template class std::vector’ // NecoErg> m3; // Error: type/value mismatch at argument 3 in template parameter list for // ‘template class Con> class NecoErg’ // NecoErg m4; } * Örnek 6, #include template T func(T x = {}) { std::cout << "x : " << x << '\n'; return x; } int main() { /* # OUTPUT # */ func(); // func(int{}); // x : 0 func(31); // x : 31 func("Ulya Yuruk"); // x : Ulya Yuruk } * Örnek 7, #include #include template> bool func(T x, F f = {}){ return f(x, x+1); } int main() { std::cout << std::boolalpha << func(5) << '\n'; // true std::cout << std::boolalpha << func(10, std::greater{}) << '\n'; // false } * Örnek 8, #include #include #include template> C func(T x = {}, U y= {}, C c = {}){ c.first = x; c.second = y; return c; } int main() { auto my_pair = func(19, 93); std::cout << "<" << my_pair.first << "," << my_pair.second << ">\n"; // <19,93> my_pair = func(); std::cout << "<" << my_pair.first << "," << my_pair.second << ">\n"; // <0,0> } >> Şimdi de "Full(Explicit) Specialization" ve "Partial Specialization" konularını ele alalım. Her iki biçimde de bizim amacımız, şablonun belli türleri için, bizim yazacağımız kodların kullanılmasını sağlatmaktır. Artık derleyici o türler için kod açılım yapmayacak ve bizim yazdığımız kodu kullanacaktır. "Partial Specialization" sadece sınıf şablonları içindir. Öte yandan sadece özelleştirilmiş versiyonları kullanacaksak, "Base Template" sınıfı/fonksiyonu sadece bildirmek yeterli gelecektir. >>> "Full(Explicit) Specialization": Bu özelleştirme türüne aynı zamanda "Diamond Specialization" da denir. Orjinal şablona "Base Template" denir. Bu tip "Specialization" yönteminde "Interface" ler aynı olmak zorunda DEĞİLDİR. Sınıf ve fonksiyon şablonları için kullanılabilir. Diğer yandan "non-type template parameter" için de özelleştirme yapabiliriz. Fakat bunun için "Base Template" in de yine "non-type template parameter" olması gerekmektedir. Diğer yandan sınıfların üye fonksiyonları için de "Full(Explicit) Specialization" yapabiliriz. Hakeza sınıfların "static" veri elemanları için de özelleştirme yapabiliriz. Yine fonksiyon şablonlarında bu özelleştirmeyi kullanırken şu noktaya dikkat etmemiz gerekmektedir; "Function Overload Resolution" a "Base Template" in kendisi girmektedir, bizim yazdığımız DEĞİL. Eğer yapılan eleme sonucunda kazanan "Base Template" ise ve çağrı sırasında da özelleştirilen tür kullanılmışsa, bizim yazdığımız kodlar çağrılacaktır. Yani ilk aşama "Base Template" in seçilmesi, devamında da uygun türün çağrı sırasında kullanılması gerekmektedir. Ek olarak, "Variable Template" lere de özelleştirme yapılabilmektedir. * Örnek 1.0, #include template struct Myclass { Myclass () { std::cout << "Primary Template for Myclass\n"; } void f1() {} void f2() {} }; template<> struct Myclass { Myclass () { std::cout << "Explicit Specialization for Myclass\n"; } void f3() {} void f4() {} }; int main() { /* # OUTPUT # Primary Template for Myclass Primary Template for Myclass Primary Template for Myclass Primary Template for Myclass Explicit Specialization for Myclass */ Myclass uc; uc.f1(); // OK Myclass sc; sc.f2(); // OK Myclass c; c.f3(); // error: ‘struct Myclass’ has no member named ‘f3’; Myclass f; f.f4(); // error: ‘struct Myclass’ has no member named ‘f4’; Myclass i; i.f1(); // error: ‘struct Myclass’ has no member named ‘f1’; i.f2(); // error: ‘struct Myclass’ has no member named ‘f2’; i.f3(); i.f4(); } * Örnek 1.1, #include template struct Myclass { Myclass () { std::cout << "Primary Template for Myclass\n"; } }; template<> struct Myclass { Myclass () { std::cout << "Explicit Specialization for Myclass\n"; } }; int main() { Myclass m1; // Primary Template for Myclass // Explicit Specialization for Myclass Myclass m2; Myclass m3; // Primary Template for Myclass } * Örnek 2, #include template struct Myclass { Myclass () { std::cout << "Primary Template for Myclass\n"; } }; template<> struct Myclass { Myclass () { std::cout << "Explicit Specialization for Myclass\n"; } }; template<> struct Myclass { Myclass () { std::cout << "Explicit Specialization for Myclass\n"; } }; int main() { /* # OUTPUT # */ Myclass m1; // Primary Template for Myclass // Explicit Specialization for Myclass Myclass m2; Myclass m3; // Primary Template for Myclass Myclass m4; // Explicit Specialization for Myclass } * Örnek 3, #include template struct Myclass { Myclass () { std::cout << "Primary Template\n"; } }; template<> struct Myclass<3> { Myclass () { std::cout << "Explicit Specialization for Myclass<3>\n"; } }; template<> struct Myclass<5> { Myclass () { std::cout << "Explicit Specialization for Myclass<5>\n"; } }; int main() { /* # OUTPUT # Explicit Specialization for Myclass<3> Explicit Specialization for Myclass<5> Primary Template */ Myclass<3> m1; Myclass<5> m2; Myclass<7> m3; } * Örnek 4, #include template struct Myclass; template<> struct Myclass<1> { Myclass () { std::cout << "Explicit Specialization for Myclass<1>\n"; } }; template<> struct Myclass<3> { Myclass () { std::cout << "Explicit Specialization for Myclass<3>\n"; } }; template<> struct Myclass<5> { Myclass () { std::cout << "Explicit Specialization for Myclass<5>\n"; } }; template<> struct Myclass<7> { Myclass () { std::cout << "Explicit Specialization for Myclass<7>\n"; } }; int main() { /* # OUTPUT # Explicit Specialization for Myclass<1> Explicit Specialization for Myclass<3> Explicit Specialization for Myclass<5> Explicit Specialization for Myclass<7> */ Myclass<1> m1; Myclass<3> m3; Myclass<5> m5; Myclass<7> m7; // Myclass<9> m9; // error: aggregate ‘Myclass<9> m9’ has incomplete type and cannot be defined } * Örnek 5, #include // C++98 template struct Factorial { static const int value = n * Factorial::value; }; template<> struct Factorial<0> { static const int value = 1; }; int main() { /* # OUTPUT # */ std::cout << "5! : " << Factorial<5>::value << '\n'; // 5! : 120 } * Örnek 6, Döngü kullanmadan, 0-100 arasındakileri ekrana yazma. #include template struct Counter : Counter { Counter() { std::cout << n << (n % 20 == 0 ? '\n' : ' '); } }; template<> struct Counter<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 */ Counter<100> m; } * Örnek 7, #include template struct Nec { void func(T) { std::cout << "Primary Template\n"; } }; template<> void Nec::func(int) { std::cout << "Explicit Template\n"; } int main() { /* # OUTPUT # Primary Template Primary Template Primary Template Explicit Template */ Nec m1; Nec m2; Nec m3; Nec m4; m1.func(1.2f); m2.func(1.2f); m3.func(1.2f); m4.func(1.2f); } * Örnek 8.0, #include template void foo(T) { std::cout << "Primary Template\n"; } template<> void foo/**/(char) { std::cout << "Specialization: foo\n"; } template<> void foo(signed char){ std::cout << "Specialization: foo\n"; } template<> void foo(unsigned char){ std::cout << "Specialization: foo\n"; } void foo(int) { std::cout << "foo(int)\n"; } void foo(float) { std::cout << "foo(float)\n"; } int main() { // Specialization: foo foo('a'); // foo(int) foo(4); // foo(float) foo(4.f); // Primary Template foo(8.); } * Örnek 8.1, #include template void func(T) { std::cout << 1; } template<> void func(int*) { std::cout << 2; } template void func(T*) { std::cout << 3; } int main() { int* p = nullptr; func(p); // 3: "1" ve "3" girer "Function Overload Resolution" a. Eğer "1" kazansaydı ve // argümanın türü "int*" olsaydı, "2" seçilecekti. } * Örnek 8.2, #include template void func(T) { std::cout << 1; } template void func(T*) { std::cout << 2; } template<> void func(int*) { std::cout << 3; } int main() { int* p = nullptr; func(p); // 3 // Şimdi yarışmaya "1" ve "2" girer. Kazanan ise "2" olur, çünkü o daha spesifik. // Fakat argümanın türü "int*" olduğundan "3" seçilir. } * Örnek 8.3, #include template void func(T) { std::cout << 1; } template<> void func(int*) { std::cout << 2; } template void func(T*) { std::cout << 3; } template<> void func(int*) { std::cout << 4; } int main() { int* p = nullptr; func(p); // 4 // "1" ve "3" yarışmaya girer ve "3" daha spesifik olduğundan kazanır. Fakat // "int*" özelleştirme yapıldığından, "4" seçilir. } * Örnek 9, #include template struct Neco{ static int x; }; // The Definition: template int Neco::x = 67; // The Specialization: template<> int Neco::x = -1; int main() { std::cout << Neco::x << '\n'; // 67 std::cout << Neco::x << '\n'; // -1 std::cout << Neco::x << '\n'; // 67 std::cout << Neco::x << '\n'; // 67 } * Örnek 10.0, "sizeof(void)" sentaks hatası oluşturur çünkü "void" türü "incomplete" türdür. #include template constexpr std::size_t SZ = sizeof(T); int main() { auto result = SZ + SZ + SZ + SZ; } * Örnek 10.1, Artık "SZ" nin "void" açılımı için özelleştirme yazdığımız için "sizeof(void)" ifadesi sentaks hatası oluşturmayacaktır. #include template constexpr std::size_t SZ = sizeof(T); template<> constexpr std::size_t SZ = 0; int main() { auto result = SZ + SZ + SZ + SZ; std::cout << result << '\n'; // 3 } >>> "Partial Specialization": "Full(Explicit) Specialization" a göre daha kapsayıcıdır. Yine "non-type template parameter" için de kullanılabilir. Yine bu tip özelleştirme "constraint" edilebilir. * Örnek 1, #include template struct Myclass { Myclass() { std::cout << "Primary Template\n"; } }; // Partial Specialization: Bütün gösterici türleri için // aşağıdaki seçilecek. template struct Myclass { Myclass() { std::cout << "Partial Template\n"; } }; int main() { Myclass m1; // Primary Template Myclass m2; // Partial Template Myclass m3; // Primary Template Myclass m4; // Partial Template } * Örnek 2.0, #include template struct Myclass { Myclass() { std::cout << "Primary\n"; } }; /* * Görüldüğü üzere "Base" olanın şablon bildiriminden * farklı biçimde bir özelleştirme yapabiliriz. Artık * aynı türler için bu, farklı türler için "Base" olan * kullanılacak. */ template struct Myclass { Myclass() { std::cout << "Partial\n"; } }; int main() { Myclass m1; // Partial Myclass m2; // Primary } * Örnek 2.1, #include template struct Myclass { Myclass() { std::cout << "Primary\n"; } }; template struct Myclass { Myclass() { std::cout << "Partial\n"; } }; int main() { Myclass m0; // Primary Myclass m1; // Partial Myclass m2; // Partial Myclass m3; // Primary // Myclass m4; // ERROR: cannot declare pointer to 'int&' } * Örnek 2.2, #include #include template struct Myclass { Myclass() { std::cout << "Base\n"; } }; template struct Myclass> { Myclass() { std::cout << "Primary\n"; } }; int main() { Myclass m1; // Base Myclass> m2; // Base Myclass> m3; // Base Myclass> m4; // Base Myclass> m5; // Primary } * Örnek 3.0, #include #include template struct IsPointer : std::false_type{}; template struct IsPointer : std::true_type{}; int main() { constexpr bool b = IsPointer::value; // true } * Örnek 3.1, #include #include template struct IsPointer : std::false_type{}; template struct IsPointer : std::true_type{}; template constexpr bool IsPointer_v = IsPointer::value; int main() { constexpr bool a = IsPointer::value; // true constexpr auto b = IsPointer_v; // true } * Örnek 4, #include #include template struct Neco{ Neco() { std::cout << "Primary\n"; } }; template struct Neco{ Neco() { std::cout << "T[]\n"; } }; template struct Neco{ Neco() { std::cout << "T[4]\n"; } }; template struct Neco{ Neco() { std::cout << "T[12]\n"; } }; int main() { Neco m; // T[] Neco m0; // Primary Neco m2; // Primary Neco m4; // T[4] Neco m6; // Primary Neco m8; // Primary Neco m10; // Primary Neco m12; // T[12] } * Örnek 5, #include #include template struct Neco{ Neco() { std::cout << "Primary\n"; } }; template struct Neco { Neco() { std::cout << "Neco\n"; } }; template struct Neco { Neco() { std::cout << "Neco\n"; } }; int main() { Neco m0; // Neco Neco m1; // Neco Neco m2; // Primary Neco m3; // Primary Neco m4; // Primary } * Örnek 6.0, #include #include #include template struct X { X() { std::cout << "Primary\n"; } }; template struct X { X() { std::cout << "integral\n"; } }; template struct X { X() { std::cout << "floating point\n"; } }; int main() { X x1; // integral X x2; // integral X x3; // floating point X x4; // Primary X x5; // Primary } * Örnek 6.1, #include #include #include #include #include #include #include template struct X { X() { std::cout << "Primary\n"; } }; template struct X { X() { std::cout << "range\n"; } }; int main() { X x1; // Primary X x2; // Primary X x3; // Primary X x5; // Primary X x4; // range X> x6; // range X> x7; // range X> x8; // Primary } >> Şimdi de "Variadic Template" konusuna değinelim. Bu tip şablonlar, parametresi parametre paketi olan şablonlardır. Yani, "Template Type Parameter", "Template Non-Type Parameter", "Template Template Parameter" türleri ile kendi içinde paketler oluşturabiliriz. * Örnek 1, // Burada "n" adet "type template parameter" // içeren bir paket oluşturduk. template class X {}; // Burada "n" farklı "non-type template parameter" // içeren bir paket oluşturduk. template class Y_1 {}; // Burada "n" adet "int" türlerinden bir paket oluşturduk. template class Y_2 {}; // Burada öyle bir paket oluşturduk ki her bir öğe, // bir adet "type template parameter" e sahip // bir "Template Template Parameter". template< templatetypename ...> class Z_1{}; // Burada öyle bir paket oluşturduk ki her bir öğe, // iki adet "type template parameter" e sahip // bir "Template Template Parameter". template< templatetypename ...> class Z_2 {}; // Burada öyle bir paket oluşturduk ki her bir öğe, // üç adet "type template parameter" e sahip // bir "Template Template Parameter". template< templatetypename ...> class Z_3 {}; // Burada öyle bir paket oluşturduk ki her bir öğe, // bir adet "int" e sahip // bir "Template Template Parameter". template< templatetypename ...> class Z_4 {}; // Burada öyle bir paket oluşturduk ki her bir öğe, // "n" adet "int" e sahip // bir "Template Template Parameter". template< templatetypename ...> class Z_5 {}; // Burada öyle bir paket oluşturduk ki her bir öğe, // "n" farklı "non-type template parameter" e sahip // bir "Template Template Parameter". template< templatetypename ...> class Z_6 {}; int main() {} * Örnek 2, Bir paket içerisinde kaç öğe olduğunu da derleme zamanı sabiti olarak öğrenebiliriz. #include template void print_elements(TypeArgs ... elements) { constexpr auto amount_TypeArgs = sizeof...(TypeArgs); constexpr auto amount_elements = sizeof...(elements); std::cout << "Amount of TypeArgs: " << amount_TypeArgs << '\n'; std::cout << "Amount of Elements: " << amount_elements<< '\n'; } int main() { // 'void print_elements(int,int,int)' // Amount of TypeArgs : 3 // Amount of Elements : 3 print_elements(3, 4, 5); // 'void print_elements(char,int,float,double,long long,long double)' // Amount of TypeArgs : 6 // Amount of Elements : 6 print_elements('a', 2, 3.f, 4., 5LL, 6.L); } * Örnek 3.0, Yine paket parametresi ile birlikte, "Type Template Parameter", "Non-Type Template Parameter", "Template Template Parameter" kullanılabilir. #include template void print_elements(T x, TypeArgs ... elements) { std::cout << "x: " << typeid(T).name() << '\n'; constexpr auto amount_TypeArgs = sizeof...(TypeArgs); constexpr auto amount_elements = sizeof...(elements); std::cout << "Amount of TypeArgs: " << amount_TypeArgs << '\n'; std::cout << "Amount of Elements: " << amount_elements<< '\n'; } int main() { // 'void print_elements(T,int,float,double,long long,long double)' // x: char // Amount of TypeArgs : 5 // Amount of Elements : 5 print_elements('a', 2, 3.f, 4., 5LL, 6.L); } * Örnek 3.1, #include template void print_elements(std::size_t x, TypeArgs ... elements) { std::cout << "n: " << typeid(n).name() << '\n'; std::cout << "x: " << typeid(x).name() << '\n'; constexpr auto amount_TypeArgs = sizeof...(TypeArgs); constexpr auto amount_elements = sizeof...(elements); std::cout << "Amount of TypeArgs: " << amount_TypeArgs << '\n'; std::cout << "Amount of Elements: " << amount_elements<< '\n'; } int main() { // 'void print_elements<5,float,double,long long,long double>(size_t,float,double,long long,long double)' // n : unsigned int // x : unsigned int // Amount of TypeArgs : 4 // Amount of Elements : 4 print_elements<5>('a', 3.f, 4., 5LL, 6.L); } Şimdi "Variadic Template" söz konusu olduğunda, birden fazla parametre kullanılacağı için, bu konuda karşımıza bir takım yöntemler çıkmaktadır. Bunlar sırasıyla, "Compile Time Recursivity", "Compile Time Recursivity with 'if constexpr'", "initializer list", "fold expression" isimli yöntemlerdir. Fakat bu teknikleri irdelemeden evvel bizlerin paketin nasıl açıldığına da değinmemiz gerekmektedir. >>> Paket Açılımı: Eğer elimizde bir liste varsa, ister sınıf şablonunda ister fonksiyon, ve uygun şartlar da sağlanmışsa, virgüller ile ayrılmış listeye bu paketi dönüştürebiliriz. İşte buna "Pack Expansion" denir ve dilin de saptadığı bir takım arayüzler, yani "pattern" ler mevcuttur. Bu arayüzlere ise "Pack Expansion Pattern" denir. * Örnek 1.0, template void func(Ts ... args) { //... // Assume 'foo' is a function; foo(args...); // foo(p1, p2, p3); foo(++args...); // foo(++p1, ++p2, ++p3); foo(&args...); // foo(&p1, &p2, &p3); foo(sizeof(args)...); // foo(sizeof(p1), sizeof(p2), sizeof(p3)); // Görüldüğü üzere "..." ifadesi, kendisinden evvelki // ifadeler neyse, onları kullanmaktadır. } int main() {} * Örnek 1.1, template void foo(Ts ... args) { //... } template void func(Ts ... args) { foo(&args...); // foo(&p1, &p2, &p3); } int main() { func(12, 45, 67); } * Örnek 1.2.0, #include int bar(int x) { std::cout << "bar(" << x << ")\n"; return x; } template void foo(Ts ... args) { //... } template void func(Ts ... args) { foo(bar(args)...); // foo(bar(p1), bar(p2), bar(p3)); // ERROR: 'bar': function does not take 3 arguments // foo(bar(args...)); // foo(bar(p1, p2, p3)); } int main() { func(12, 45, 67); // bar(67) // bar(45) // bar(12) } * Örnek 1.2.1, #include int bar(int x) { std::cout << "bar(" << x << ")\n"; return x; } template void foo(Ts ... args) { //... } template void func(Ts ... args) { foo(bar(args*args)...); // foo(bar(p1), bar(p2), bar(p3)); } int main() { func(12, 45, 67); // bar(4489) // bar(2025) // bar(144) } * Örnek 1.3, template void func(Args... args) { // 'foo' nun bir fonksiyon olduğunu ve 'func' // fonksiyonuna da dört tane argüman gönderildiğini // varsayalım. Bu durumda 'foo' fonksiyonunu şu // şekilde görebiliriz; // foo(p1, p2, p3, p4); // Buradaki önemli nokta, parametre paketinden sonra // "..." atomunun gelmesidir. foo(args...); } * Örnek 1.4.0, #include #include template void print(const Ts&... args) { // error C3520: 'args': parameter pack must be expanded in this context // ((std::cout << args << ' '), 0); // -----> (1) /* -----> (1) * "((std::cout << args << ' '), 0)" ifadesiyle birlikte * "args" değeri ekrana yazdırılacak. İfadenin geri dönüş * değeri "0" olacak. Fakat paket açılımı yapmadığımız için * sentaks hatası alacağız. */ // {((std::cout << args << ' '), 0)...}; // -----> (2) /* -----> (2) * "{((std::cout << args << ' '), 0)...}" ifadesiyle bizler * hem ekrana "args" değerini yazıyor, hem "," operatörünün * geri dönüş değeri ile bir "initializer_list" oluşturuyor * hem de paket açılımı yapmaktayız. İşte ortaya çıkarılan * iş bu "initializer_list" i bir "container" için ilk değer * vermekte kullanabiliriz. */ // int a[] = { ((std::cout << args << ' '), 0)... }; // -----> (3) /* -----> (3) * Artık "a" dizimizin elemanları, parametre paketindeki * öğe sayısı kadar "0" elemanı içermektedir. Fakat buradaki * problem ise bu dizinin tanımlanmış fakat daha sonra * kullanılmamış olmasıdır. Böylesi bir dizi yerine * "std::initializer_list" in "int" açılımı türünden geçici * bir nesne oluşturabiliriz. */ // (void)std::initializer_list{ ((std::cout << args << ' '), 0)... }; // -----> (4) /* -----> (4) * Artık "std::initializer_list" in "int" açılımı türünden geçici * bir nesne oluşturduk. Statik kod analiz programları ve/veya * derleyici programlar herhangi bir uyarı vermesin diye de bu * nesneyi "void" türüne cast ettik. Artık "print" fonksiyonuna * gönderilen argümanları daha etkili bir biçimde ekrana yazdırabiliriz. */ using extender = int[]; (void)extender { ((std::cout << args << ' '), 0)... }; // -----> (5) /* -----> (5) * Fakat o yöntem de yazım kalabalığı oluşturduğu için * biraz zahmetli. İşte adına "Poor Man's Fold Expression" * da denilen yukarıdaki yöntemle, yazma zahmetinden de * kurtulmuş olmaktayız. */ } int main() { print('a', 2, 4.4, "ulya yuruk"); // a 2 4.4 ulya yuruk } * Örnek 1.4.1, "," operatöründen faydalanamak. #include // Old School: //template //void print(Args...args) { // C++20, Aggreviated Template Syntax: void print(auto... args) { ((std::cout << args << ' '), ...); // Unary Left Fold Expression std::cout << '\n'; } auto g(auto* x) { return *x * *x + 10; } // Old School: //template //void f(Args...args) { // C++20, Aggreviated Template Syntax: void f(auto... args) { // Assuma 3 arguments were used to call "f", // called "p1", "p2" and "p3". // Kural Şudur: // "..." atomu, kendisinden evvelki en uzun // ifadeye ele almaktadır. // # Pattern # # Function Call # # Result # print(args...); // args print(p1, p2, p3); 1 2 3 print(&args...);// &args print(&p1, &p2, &p3); 007DF7A0 007DF7A4 007DF7A8 print(10 * args...);// 10*args print(10*p1, 10*p2, 10*p3); 10 20 30 print(args * args...);// args*args print(p1*p1, p2*p2, p3*p3); 1 4 9 print(g(&args)...);// g(&args) print(g(&p1), g(&p2), g(&p3)); 11 14 19 print(++args...);// ++args print(++p1, ++p2, ++p3); 2 3 4 } int main() { int x{ 1 }, y{ 2 }, z{ 3 }; f(x, y, z); } * Örnek 1.4.2, #include #include template void bar(Ts&&... args) { int cnt{}; ( ( std::cout << ++cnt << ". argument is : " << (std::is_lvalue_reference_v ? "L-Value" : "R-Value") << '\n' ) , ... ); } template void foo(Ts&&... args) { bar( std::forward(args)... ); // # Pattern # // std::forward(args) // # Function Call # // bar( // std::forward(p1), // std::forward(p2), // std::forward(p3), // std::forward(p4) // ); } int main() { int x{}; double y{}; // void bar // (int &,_Ty &&,_Ty &&,double &,double &&,const char (&)[11]) // void foo // (int &,int &&,int &&,double &,double &&,const char (&)[11]) foo(x, 34, std::move(x), y, +y, "Ulya Yuruk"); /* # OUTPUT # 1. argument is : L-Value 2. argument is : R-Value 3. argument is : R-Value 4. argument is : L-Value 5. argument is : R-Value 6. argument is : L-Value */ } * Örnek 1.5, #include // C++20: // auto square(auto x) { // Old School: template T square(T x) { return x * x; } // C++20 auto sum(auto...args) { return (... + args); } template void call_sum(T... args) { auto x1 = sum(args...); // sum(1, 2, 3, 4, 5); std::cout << "x1: " << x1 << '\n'; // 15 x1 = sum(85, args...); // sum(85, 1, 2, 3, 4, 5); std::cout << "x1: " << x1 << '\n'; // 100 x1 = sum(square(args)...); // sum(1, 4, 9, 16, 25); std::cout << "x1: " << x1 << '\n'; // 55 } int main() { call_sum(1, 2, 3, 4, 5); } * Örnek 1.6, template void f(Us... pargs) {} template void g(Ts... args) { // # Pattern # # Function Call # # Result # g(&args...); // &args f(&p1, &p2, &p3) } int main() { // "Ts..." will expand to "int", "double", "char" for // "p1", "p2" and "p3", respectivelly. // So, "&args" means that "&p1", "&p2" and "&p3". // So, "Us..." will expand to "int*", "double*", "char*" for // "p1", "p2" and "p3", respectivelly. g(1, .2, 'u'); } * Örnek 1.7, #include template constexpr auto sum(Ts... args) { return (0 + ... + args); // Binary Fold Expression // Bu fonksiyona geçilen argümanların türleri farklı // olabilir. Örneğin, "int", "float" ve "double". // Dolayısıyla "constraint" edilmediğinden, toplama // işlemi sırasında dikkatli olmalıyız. } template constexpr auto multiply(Ts... args) { return (1 * ... * args); } template constexpr auto foo(Ts... args) { return multiply(sum(args...) + args...); // Patterns: | | sum(args...) + args // | sum(args...) } int main() { constexpr auto result1 = foo(1, 2, 4); // 792 /* # Function Call # multiply( sum(1, 2, 4) + 1, // 8 sum(1, 2, 4) + 2, // 9 sum(1, 2, 4) + 4 // 11 ); // multiply(8, 9, 11); */ constexpr auto result2 = foo('a', 2., 16LL); // 3'249'324.0 /* # Function Call # multiply( sum(97, 2., 16) + 97, // 212 sum(97, 2., 16) + 2., // 117 sum(97, 2., 16) + 16 // 131 ); // multiply(212, 117, 131); */ } * Örnek 1.8, #include #include template class Myclass { //... std::tuple mx; }; int main() { Myclass a; // mx: std::tuple } * Örnek 1.9, #include #include template class Myclass { public: static constexpr size_t sz = sizeof...(Args); }; int main() { Myclass a; // sz = 3; } * Örnek 1.10, #include #include template auto make_an_empty_array(Args... args) { return std::array{}; // "sizeof...(args)" ve "sizeof...(Args) => "sizeof" operatörü; } template auto make_a_filled_array(Args... args) { return std::array{sizeof(args)...}; // "sizeof(Args)..." ve "sizeof(args)..." => "Pack Expansion" => "sizeof(T1), sizeof(T2)... } template auto make_a_real_array(Args... args) { return std::array{args...}; // "sizeof(Args)..." ve "sizeof(args)..." => "Pack Expansion" => "sizeof(T1), sizeof(T2)... } int main() { // "arr" is of type "std::array('a', 30, 19.31f); for (auto i : arr) { std::cout << i << ' '; // 0 0 0 } std::cout << '\n'; // "arr2" is of type "std::array('a', 30, 19.31f); for (auto i : arr2) { std::cout << i << ' '; // 1 4 4 } std::cout << '\n'; // ERROR: "int" to "std::size_t" && "float" to "std::size_t" is // narrowing conversion. // WARNING: "float" to "std::size_t" is possible loss of data. // auto arr3 = make_a_real_array('a', 30, 19.31f); auto arr3 = make_a_real_array(30, 45000, 1993); for (auto i : arr3) { std::cout << i << ' '; // 30 45000 1993 } } * Örnek 1.11, #include constexpr int square(int N) { return N * N; } template void print_array(int(&arr)[N]) { for (int i : arr) std::cout << i << ' '; std::cout << '\n'; } // Bu fonksiyona gönderilen argümanların // hepsinin "int" olması gerekmektedir. void foo(std::same_as auto... args) { int a1[] = { args... }; // "a1" contains elements "{1, 3, 5, 7}"; print_array(a1); // 1 3 5 7 int a2[] = { args..., 0 }; // "a2" contains elements "{1, 3, 5, 7, 0}"; print_array(a2); // 1 3 5 7 0 int a3[sizeof...(args) + 2] = { -1, args..., -1 }; // "a3" is "int[6]{-1, 1, 3, 5, 7, -1}"; print_array(a3); // -1 1 3 5 7 -1 int a4[] = { square(args)... }; // "a4" contains "{1, 9, 25, 49}"; print_array(a4); // 1 9 25 49 } int main() { foo(1, 3, 5, 7); } * Örnek 2.0, Usage in Inheritence #include template class Base { public: Base() { std::cout << typeid(Base).name() << '\n'; } }; template class Der : Base { public: Der() { std::cout << typeid(Der).name() << '\n'; } }; int main() { /* # OUTPUT # class Base class Der */ Der myder; } * Örnek 2.1, #include template class Base { public: Base() { std::cout << typeid(Base).name() << '\n'; } }; template class Der : Base { public: Der() { std::cout << typeid(Der).name() << '\n'; } }; int main() { /* # OUTPUT # class Base class Der */ Der myder; } * Örnek 2.2, #include template class Base { public: Base() { std::cout << typeid(Base).name() << '\n'; } }; template class Der : Base... { public: Der() { std::cout << typeid(Der).name() << '\n'; } }; int main() { /* # OUTPUT # class Base class Base class Base class Der */ Der myder; } * Örnek 2.3.0, Usage in Multiple Inheritence. More than one base class was used. #include #include struct A { A(int x = 0) { std::cout << "A(" << x << ")\n"; } }; struct B { B(int x = 0) { std::cout << "B(" << x << ")\n"; } }; struct C { C(int x = 0) { std::cout << "C(" << x << ")\n"; } }; template struct ABC : public Ts... { ABC(int x) { std::cout << "ABC(" << x << ")\n"; } }; int main() { /* # OUTPUT # A(0) B(0) C(0) ABC(1993) */ ABC abc(1993); } * Örnek 2.3.1, #include #include struct A { A(int x = 0) { std::cout << "A(" << x << ")\n"; } void fA() const { std::cout << "fA()\n"; } }; struct B { B(int x = 0) { std::cout << "B(" << x << ")\n"; } void fB() const { std::cout << "fB()\n"; } }; struct C { C(int x = 0) { std::cout << "C(" << x << ")\n"; } void fC() const { std::cout << "fC()\n"; } }; template struct ABC : public Ts... { ABC(int x) { std::cout << "ABC(" << x << ")\n"; } }; int main() { /* # OUTPUT # A(0) B(0) C(0) ABC(1993) fA() fB() fC() */ ABC abc(1993); abc.fA(); abc.fB(); abc.fC(); } * Örnek 2.3.2, #include #include struct A { A(int x) { std::cout << "A(" << x << ")\n"; } }; struct B { B(int x) { std::cout << "B(" << x << ")\n"; } }; struct C { C(int x) { std::cout << "C(" << x << ")\n"; } }; template struct ABC : public Ts... { // -----> (1), Uncomment the code below: -----> (2) ABC(int x) /* :Ts{x}... */ { std::cout << "ABC(" << x << ")\n"; } }; int main() { /* // -----> (2), The output'd be: # OUTPUT # A(1993) B(1993) C(1993) ABC(1993) */ // no appropriate default constructor available for "A", "B", "C" // -----> (1) ABC abc(1993); } * Örnek 2.3.3, #include #include struct X { X(int i) { std::cout << "X(int i) i = " << i << '\n'; } }; struct Y { Y(int i) { std::cout << "Y(int i) i = " << i << '\n'; } }; struct Z { Z(int i) { std::cout << "Z(int i) i = " << i << '\n'; } }; template struct XYZ : public Types... { // Yukarıdaki paket açılımı ile "XYZ" sınıfı birden fazla // taban sınıfa sahip olabilir. XYZ() : Types{ 31 }... { // Yukarıdaki paket açılımında, taban sınıfın "ctor." fonksiyonlarına // "31" değeri gönderilecektir. } }; int main() { XYZ ax; //X(int i) i = 31 //Y(int i) i = 31 //Z(int i) i = 31 using type = decltype(ax); static_assert( std::is_base_of_v && std::is_base_of_v && std::is_base_of_v ); // Holds } * Örnek 2.3.4.0, Görüldüğü üzere taban sınıflarda bulunan fonksiyonlar türemiş sınıfa da gelmiş oldu. #include #include struct X { void fX(int x) { std::cout << "X::fX(int x) x = " << x << '\n'; } }; struct Y { void fY(double x) { std::cout << "Y::fY(double x) x = " << x << '\n'; } }; struct Z { void fZ(char x) { std::cout << "Z::fZ(char x) x = " << x << '\n'; } }; template struct XYZ : public Types... { }; int main() { /* # OUTPUT # X::fX(int x) x = 31 Y::fY(double x) x = 3.1 Z::fZ(char x) x = u */ XYZ ma; ma.fX(31); ma.fY(3.1); ma.fZ('u'); } * Örnek 2.3.4.1, Pekiyi taban sınıflardaki fonksiyonların isimleri aynı fakat parametreleri farklı olsaydı ne olacaktı? Tabii ki "ambiguity". Çünkü üç isim de türemiş sınıfta görünür vaziyettedir. #include #include struct X { void foo(int x) { std::cout << "X::foo(int x) x = " << x << '\n'; } }; struct Y { void foo(double x) { std::cout << "Y::foo(double x) x = " << x << '\n'; } }; struct Z { void foo(char x) { std::cout << "Z::foo(char x) x = " << x << '\n'; } }; template struct XYZ : public Types... { // -----> (1) // using Types::foo...; // Uncomment this code to compile. }; int main() { /* # OUTPUT # X::foo(int x) x = 31 Y::foo(double x) x = 3.1 Z::foo(char x) x = u */ XYZ ma; // error C2385: ambiguous access of 'foo' ma.foo(31); // -----> (1) // error C2385: ambiguous access of 'foo' ma.foo(3.1); // -----> (1) // error C2385: ambiguous access of 'foo' ma.foo('u'); // -----> (1) } * Örnek 2.4, #include #include template class Var {}; template class Myclass : public Var { public: static constexpr size_t size = sizeof...(Types); }; int main() { constexpr auto n = Myclass::size; // 2 static_assert(std::is_base_of_v, Myclass>); // Holds } * Örnek 2.5, #include #include template class Var { public: Var() { std::cout << typeid(Var).name() << '\n'; } }; template class Myclass : public Var { public: }; int main() { Myclass m; // class Var } * Örnek 3.0, Printing type names: #include #include template void my_tuple(A p1, B p2, OtherTypes... pOthers) { std::tuple the_tuple{p1, p2, pOthers...}; std::cout << "<" << typeid(the_tuple).name() << '\n'; std::cout << "<" << typeid(std::get<0>(the_tuple)).name() << "," << typeid(std::get<1>(the_tuple)).name() << "," << typeid(std::get<2>(the_tuple)).name() << "," << typeid(std::get<3>(the_tuple)).name() << "," << typeid(std::get<4>(the_tuple)).name() << "," << ">"; std::cout << "\n"; std::cout << "<" << std::get<0>(the_tuple) << "," << std::get<1>(the_tuple) << "," << std::get<2>(the_tuple) << "," << std::get<3>(the_tuple) << "," << std::get<4>(the_tuple) << "," << ">"; } int main() { /* # OUTPUT # <2,3.4,Q,14,5.6,> */ my_tuple(2, 3.4, 'Q', 14, 5.6); } * Örnek 3.1, #include #include template void my_tuple(A p1, B p2, OtherTypes... pOthers) { std::tuple the_tuple{p1, p2, pOthers...}; std::cout << "<" << typeid(the_tuple).name() << '\n'; std::cout << "<" << typeid(std::get<0>(the_tuple)).name() << "," << typeid(std::get<1>(the_tuple)).name() << "," << typeid(std::get<2>(the_tuple)).name() << "," << typeid(std::get<3>(the_tuple)).name() << "," << typeid(std::get<4>(the_tuple)).name() << "," << ">"; std::cout << "\n"; std::cout << "<" << std::get<0>(the_tuple) << "," << std::get<1>(the_tuple) << "," << std::get<2>(the_tuple) << "," << std::get<3>(the_tuple) << "," << std::get<4>(the_tuple) << "," << ">"; } int main() { /* # OUTPUT # <2,3,14.56,14,5.6,> */ my_tuple(2, 3.4, 14.56f, 14, 5.6); } * Örnek 3.2, #include #include template void print_type(const T&) { std::cout << typeid(T).name() << '\n'; } template void func(A arg1, B arg2, Ts... args) { std::tuple t1; // std::tuple t1; print_type(t1); // class std::tuple std::tuple t2; // std::tuple t1; print_type(t2); // class std::tuple std::tuple t3; // std::tuple t1; print_type(t3); // class std::tuple } int main() { // void func // (A,B,double,long,char) func(1, 1.2f, 3.4, 5L, 'A'); } * Örnek 3.3, #include template void print_type(const T&) { std::cout << typeid(T).name() << '\n'; } template class Myclass { public: Myclass() { print_type(this); } Myclass(Args...) { print_type(this); } }; template void func(Ts... args) { Myclass m1; Myclass m2(args...); } template void foo(Ts&&... args) { Myclass m3(std::forward(args)...); } int main() { foo(17, 1.7, 1993LL, 19.93f, "UlyaYuruk"); // class Myclass std::cout << "\n=====================\n"; func(17, 1.7, 1993LL, 19.93f, "UlyaYuruk"); //class Myclass //class Myclass } * Örnek 3.4, #include template struct A { A() { std::cout << typeid(A).name() << '\n'; } }; template void func() { A ax; } int main() { func<1, 2, 3, 4>(); // struct A<1,2,3,4> } * Örnek 3.5, #include #include #include template struct Nec { // "mx" değişkeninin değeri, parametre paketindekilerin // toplamı kadar olacaktır. Nec(Ts... args) : mx{ (... + args) } {} // Parametre paketindeki türler "int", "long", "double" olması durumunda // "mx" değişkeninin türü, bu üç türün "common_type" türü olan "double" // olacaktır. std::common_type_t mx; }; template void foo(Ts...args) { // Yine "a" dizisinin türü, parametre paketinde "int", "long", "double" // olması halinde, "double" olacaktır. "sizeof..." ifadesiyle de bizler // parametre paketindeki eleman sayısını temin etmiş olacağız. Diğer // yandan "a" dizisine ilk değer veren "args..." ifadesiyle de bir // "initializer_list" oluşturmuş oluyoruz. std::array, sizeof...(Ts)> a{ args... }; std::cout << "Type: " << typeid(a).name() << '\n'; // ----->(1), Type: class std::array // ----->(2), Type: class std::array Nec nec1(args...); std::cout << "Type: " << typeid(nec1).name() << '\n'; // ----->(1), Type : struct Nec // ----->(2), Type : struct Nec std::cout << "nec1.mx: " << nec1.mx << '\n'; // ----->(1), nec1.mx : 60 // ----->(2), nec1.mx : 60 Nec nec2(--args...); std::cout << "Type: " << typeid(nec2).name() << '\n'; // ----->(1), Type : struct Nec // ----->(2), Type : struct Nec std::cout << "nec2.mx: " << nec2.mx << '\n'; // ----->(1), nec2.mx : 57 // ----->(2), nec2.mx : 57 } int main() { foo(10, 20L, 30.); // ----->(1) foo(10, 20, 30); // ----->(2) } * Örnek 3.6, #include template struct Neco { // This member template's parameter type is // "Non-Type Template Parameter". template struct Nested { }; }; using type_encloser = Neco; // Error: type name is not allowed. // using type_nested = type_encloser::Nested; using type_nested = type_encloser::Nested<10, 4.5, 56L, 'A'>; int main() { std::cout << typeid(type_encloser).name() << '\n'; // struct Neco std::cout << typeid(type_nested).name() << '\n'; // struct Neco::Nested<10, 4.500000, 56, 65> } * Örnek 4, Şablon parametresi birden fazla parametre paketi kullanılabilir. #include template void func(Types(&...args)[N]) { std::cout << __FUNCSIG__ << '\n'; } int main() { char a[1]{}; func(a); // void func(char(&)[1]) unsigned char b[2]{}; func(b); // void func(unsigned char(&)[2]) signed char c[3]{}; func(c); // void func(signed char(&)[3]) float d[4]{}; func(d); // void func(float(&)[4]) double e[5]{}; func(e); // void func(double(&)[5]) long double f[6]{}; func(f); // void func(long double(&)[6]) int g[7]{}; func(g); // void func(int(&)[7]) unsigned int h[8]{}; func(h); // void func(unsigned int(&)[8]) long int i[9]{}; func(i); // void func(long(&)[9]) long long j[10]{}; func(j); // void func<__int64, 0xa>(__int64(&)[10]) std::cout << "\n======================\n"; func(a, b, c, d, e, f, g, h, i, j); /* void func < char, unsigned char, signed char, float, double, long double, int, unsigned int, long, __int64, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa > ( char(&)[1], unsigned char(&)[2], signed char(&)[3], float(&)[4], double(&)[5], long double(&)[6], int(&)[7], unsigned int(&)[8], long(&)[9], __int64(&)[10] ) */ } Şimdi yukarıda isimleri zikredilen yöntemleri açıklayabiliriz: >>> "Compile Time Recursivity" : Derleme zamanında "recursive" biçimde derleyiciye kod yazdırtmaktır. Fakat burada bizim "base" bir durumu da belirtmemiz gerekiyor ki derleyici sonsuza kadar kod yazmaya devam etmesin. * Örnek 1, Bu yönteme ilişkin en tipik örnek, "n" tane argümanın yazdırılmasıdır. #include template void print(const T& r) { std::cout << r << '\n'; } template void print(const T& r, const TemplateArgs&... elements) { print(r); print(elements...); } int main() { /* # OUTPUT # a b c -1 1 10 1 1 10 Ulya Yuruk */ signed char a = 'a'; char b = 'b'; unsigned char c = 'c'; int d = -1; unsigned int e = 1; long long f = 10LL; float g = 1.f; double h = 1.f; long double i = 10.L; const char* name = "Ulya Yuruk"; print(a, b, c, d, e, f, g, h, i, name); } >>> "initializer list": Burada ise "std::initializer_list" ve "," operatörünün yoğun kullanımı mevcuttur. Anımsanacağı üzere "," operatörünün ürettiği değer, sağındaki ifadedir. Fakat solundaki ifadeyi de öncesinde işletir. * Örnek 1, #include #include template void print(const Args& ...args) { (void)std::initializer_list{ (std::cout << args << '\n', 0)... }; // Burada "," operatörünün solundaki "std::cout << args << ' '" // ifadesi işletildikten sonra "discard" edilecek ve geri dönüş değeri // olarak "0" ifadesini döndürecek. Dolayısıyla paket açılırken öğeler // ekrana yazdırılacak, sonrasında "0" değeri "initializer_list" e eklenecek. // Dolayısıyla paketteki son öğe de açılıp değeri ekrana yazıldıktan sonra, // paketteki öğe sayısınca "0" değeri "initializer_list" içerisinde olacak. // Fakat bu geçici nesne olduğundan, hayatı da sona erecek. } int main() { /* # OUTPUT # a b c -1 1 10 1 1 10 Ulya Yuruk */ signed char a = 'a'; char b = 'b'; unsigned char c = 'c'; int d = -1; unsigned int e = 1; long long f = 10LL; float g = 1.f; double h = 1.f; long double i = 10.L; const char* name = "Ulya Yuruk"; print(a, b, c, d, e, f, g, h, i, name); } >>> "Fold Expression" : C++17 ile dile eklenmiştir. İfade parantez içerisinde olmak zorundadır. "Binary Operators" için kullanılan bir sentakstır. İki farklı kategoriye ayrılır; "Unary Fold" ve "Binary Fold". Her iki yöntemde de "Left" ve "Right" kavramı vardır. Dolayısıyla kullanılan operatörler sonucuna belirlenmesinde bir fark oluşturmazken, bazı operatörler oluşturabilir. Dolayısıyla dikkatli olmalıyız. Öte yandan parametre paketinin boş olması "Unary Fold" için sentaks hatası oluşturur eğer kullanılan operatör "&&", "||" ve "," operatörlerinden BİRİ DEĞİLSE. Bu üç operatörünün kullanıldığı durumlarda, parametre paketinin boş olması, "Unary Fold" için SENTAKS HATASI OLUŞTURMAZ. Yine paketin boş olması durumunda, bu operatörlerden "&&" ve "||" sırasıyla "true" ve "false" değer döndürürken, "," operatörü ise "void" türden bir ifade OLUŞTURUR. Hakeza buradaki "&&" ve "||" operatörleri yine kısa devreye sebep olacaktır. "Binary Fold" için operatör kullanımına ilişkin böyle bir kural yoktur, parametre paketinin boş olması bir sentaks hatasına yol açmaz. >>>> "Unary Fold" : Herhangi bir "Binary Operator" ü ele alalım. Bu operatörün bir operandını "...", diğer operandını da parametre paketi yapıyoruz. Bu durumda "...", operatörün solunda ise, ilgili "Fold Expression" için "Unary Left Fold", sağında ise "Unary Right Fold" denir. >>>>> "Unary Left Fold": * Örnek 1, "+" operatörünü ele alalım. Parametre paketinde ise "p1", "p2", "p3" ve "p4" elemanları olsun. "(... + args)" ifadesinin açılımı bir nevi şöyledir: "(((p1+p2) + p3) + p4)" >>>>> "Unary Right Fold": * Örnek 1, "+" operatörünü ele alalım. Parametre paketinde ise "p1", "p2", "p3" ve "p4" elemanları olsun. "(args + ...)" ifadesinin açılımı bir nevi şöyledir: "(p1 + (p2 + (p3+p4)))" >>>> "Binary Fold" : "Unary Fold" da olduğu gibi "Left" ve "Right" kavramı burada da vardır. Bu yöntemde ise ilgili operatör iki defa yazılır ve bu iki operatörün arasına "..." gelir. Operatörlerin ikisi de AYNI OLMAK ZORUNDADIR. >>>>> "Binary Left Fold": * Örnek 1, "+" operatörünü ele alalım. Parametre paketinde ise "p1", "p2", "p3" ve "p4" elemanları olsun. "(init + ... + args)" Buradaki "init" ifadesi olarak da "0" değerini kullanalım. Dolayısıyla ifadenin açılımı bir nevi şöyledir: "((((0+p1) + p2) + p3) + p4)" >>>>> "Binary Right Fold": * Örnek 1, "+" operatörünü ele alalım. Parametre paketinde ise "p1", "p2", "p3" ve "p4" elemanları olsun. "(args + ... + init)" Buradaki "init" ifadesi olarak da "0" değerini kullanalım. Dolayısıyla ifadenin açılımı bir nevi şöyledir: "(p1 + (p2 + (p3 + (p4+0))))" Açılımlardan da görüleceği üzere "init" ifadesi, "initializer" olarak işlev görmekte. Yani bir nevi başlangıç noktası gibi. Özetle "..." atomunun "args" ifadesinin sağında yer alıyorsa "Right", solunda yer alıyorsa "Left" versiyonu işletilecektir. Şimdi de örneklere geçelim; * Örnek 1.0, Summing variables: #include template auto sum(const Args& ...args) { return (args + ...); } int main() { /* # OUTPUT # a b c -1 1 10 1 1 10 Ulya Yuruk */ signed char a = 'a'; char b = 'b'; unsigned char c = 'c'; int d = -1; unsigned int e = 1; long long f = 10LL; float g = 1.f; double h = 1.f; long double i = 10.L; std::cout << sum(a, b, c, d, e, f, g, h, i); // 316 } * Örnek 1.1, #include #include template auto sum(Ts&&... args) { return (args + ...); // return (std::forward(args) + ...); // Sınıf türleri için önemli olabilir. } int main() { std::cout << sum(12, 4.5, 50L, 'a') << '\n'; // (12 + (4.5 + (50+97))) // 163.5 } * Örnek 1.2, #include #include template constexpr auto sum_left(Ts... args) { return (... + args); // Unary Left Fold } template constexpr auto sum_right(Ts... args) { return (args + ...); // Unary Right Fold } int main() { using namespace std::literals; { constexpr auto result_left= sum_left(1, 3, 5, 7, 9); std::cout << "Result of Left: " << result_left << '\n'; // Result of Left : 25 constexpr auto result_right = sum_right(9, 7, 5, 3, 1); std::cout << "Result of right: " << result_right << '\n'; // Result of right : 25 } std::cout << '\n'; { auto result_left = sum_left("Ulya"s, "Yuruk"s, "Uskudar", "Istanbul"); std::cout << "Result of Left: " << result_left << '\n'; // Result of Left : UlyaYurukUskudarIstanbul auto result_right = sum_right("Istanbul", "Uskudar", "Yuruk"s, "Ulya"s); std::cout << "Result of right: " << result_right << '\n'; // Result of right : IstanbulUskudarYurukUlya } std::cout << '\n'; { auto result_left = sum_left("Ahmet"s, "Kandemir", "Pehlivanli", "Istanbul"); std::cout << "Result of Left: " << result_left << '\n'; // Result of Left : AhmetKandemirPehlivanliIstanbul // auto sum_right // (std::string,const char *,const char *,const char *) // ERROR: error C2110: '+': cannot add two pointers // auto result_right = sum_right("Ahmet"s, "Kandemir", "Pehlivanli", "Istanbul"); // std::cout << "Result of right: " << result_right << '\n'; // Result of right : // Burada "Pehlivanli" ve "Istanbul" yazıları "pointer" türüne "decay" olacaktır. // İki göstericiyi bu şekilde toplayamadığımız için sentaks hatası alacağız. } } * Örnek 1.3, Summing variables using Variable Templates: #include template constexpr int Sum = (... + Vals); template constexpr int SumSquare = (... + (Vals * Vals)); int main() { std::cout << Sum<1> << '\n'; // 1 std::cout << Sum<1, 9> << '\n'; // 10 std::cout << Sum<1, 123, 321> << '\n'; // 445 std::cout << '\n'; std::cout << SumSquare<1> << '\n'; // 1 /* # Function Call # (1*1) */ std::cout << SumSquare<1, 9> << '\n'; // 82 /* # Function Call # ((1*1 + 9*9)) */ std::cout << SumSquare<1, 123, 321> << '\n'; // 118171 /* # Function Call # ((1*1 + 123*123) + 321*321) */ } * Örnek 2, Dividing variables: #include #include template auto f_div_r(Ts&&... params) { return (std::forward(params) / ...); // Unary Right Fold } template auto f_div_l(Ts&&... params) { return (... / std::forward(params)); // Unary Right Fold } int main() { std::cout << f_div_r(500, 50, 5, 2) << '\n'; // 20 // (500 / (50 / (5/2))) std::cout << f_div_l(500, 50, 5, 2) << '\n'; // 1 // (((500 / 50) / 5) / 2); } * Örnek 3, Counting words: #include #include #include #include #include template auto matches(const C& range, const Ts& ...params) { return ( std::count( begin(range), end(range), params ) + ... ); /* // -----> (1) # Function Call # ( (std::cout(begin(), end(), p1) + (std::cout(begin(), end(), p2) + (std::cout(begin(), end(), p3) + (std::cout(begin(), end(), p4) + std::cout(begin(), end(), p5))))) ); // -----> (2) # Function Call # ( (std::cout(begin(), end(), p1) + std::cout(begin(), end(), p2)) ); */ } int main() { std::vector ivec{ 23, 4, 78, 12, 9, 23, 4, 2, 13, 78, 12, 9, 23, 9, 23, 4 }; std::cout << matches(ivec, 4, 2, 13, 45, 120) << '\n'; // 5 // -----> (1) // Sırasıyla "4", "2", "13", "45" ve "120" rakamlarını // "ivec" içerisinde kaçar adet olduğuna bakacak. Sonuç // olarak da bu adetlerin toplam değerini bize döndürecek. // "4" : 3 // "2" : 1 // "13" : 1 // "45" : 0 // "120" : 0 // Toplam: 5 std::cout << "Enter a sentence: "; std::string str; std::getline(std::cin, str); std::cout << matches(str, 'u', 'y') << '\n'; // 4 // -----> (2) // Girilen cümledeki "u" ve "y" harflerinin toplam // kaç adet olduğunu geri döndürecektir. // Ulya Yuruk Uskudar // "u" : 3 // "y" : 1 // Toplam: 4 } * Örnek 4.0, "Unary Fold" için "&&", "||" ve "," operatörlerinin kullanımı: #include #include auto fold_and(auto... args) { return (args && ...); } auto fold_or(auto... args) { return (args || ...); } auto fold_comma(auto... args) { return (args , ...); } int main() { std::boolalpha(std::cout); std::cout << fold_and() << '\n'; // True std::cout << fold_or() << '\n'; // False static_assert(std::is_void_v); // Holds } * Örnek 4.1, #include #include constexpr auto all(std::convertible_to auto... args) { return (... && args); // (((p1 && p2) && p3) && p4) } int main() { constexpr bool result = all(true, 10 < 20, 5 == 5); // true } * Örnek 4.2, #include template bool all_in_range(const T& min, const T& max, Args&&... params) { /* // Sınıflar söz konusu olduğunda işlevsel olabilir: return ( ( min <= std::forward(params) && max >= std::forward(params) ) && ... ); */ return ( ( min <= params && max >= params ) && ... ); } int main() { std::boolalpha(std::cout); std::cout << all_in_range(1, 20, 2, 4, 6, 7, 9) << '\n'; // True // "2, 4, 6, 7, 9" değerleri, "[1,20]" aralığında mı? std::cout << all_in_range(10, 200, 20, 40, 60, 70, 900) << '\n'; // False // "20, 40, 60, 70, 900" değerleri, "[10,200]" aralığında mı? } * Örnek 4.3, #include #include #include template bool insert(S& r_set, Args&&... args) { return ( r_set.insert( std::forward(args) ).second && ... ); /* Burada "S" için "std::set" geldiğini varsayarsak; * "std::set" in ".insert()" fonksiyonu bir "std::pair" * döndürmektedir. Bu "std::pair" in "first" ve "second" * değerleri sırasıyla; * Ekleme yapılmışsa, eklenen öğeye ilişkin iteratör ve "true" * değerlerine sahip olur. * Ekleme yapılmamışsa, halihazırdaki öğeye ilişkin iteratör ve * "false" değerlerine sahip olur. */ } int main() { std::boolalpha(std::cout); std::set my_set{ 34, 56, 98 }; std::cout << "Size: " << my_set.size() << '\n'; // Size: 3 std::cout << insert(my_set, 17, 9, 1993) << '\n'; // true std::cout << "Size: " << my_set.size() << '\n'; // Size : 6 std::cout << insert(my_set, 24, 7, 1995) << '\n'; // true std::cout << "Size: " << my_set.size() << '\n'; // Size : 9 std::cout << insert(my_set, 10, 11, 1995) << '\n'; // false std::cout << "Size: " << my_set.size() << '\n'; // Size : 11 } * Örnek 4.4, #include #include #include void print(const auto&... args) { std::cout << "Function Signature: " << __FUNCSIG__ << '\n' << "Values: "; ((std::cout << args << ' '), ...); } int main() { std::string name{ "Ulya Yuruk, Istanbul" }; std::bitset<16> bs{ 34567u }; print(28.2, name, bs, "Uskudar", 1995); /* // Function Signature: void print < double, class std::basic_string< char, struct std::char_traits, class std::allocator >, class std::bitset<16>, char[8], int > ( const double &, const class std::basic_string< char, struct std::char_traits, class std::allocator > &, const class std::bitset<16> &, const char (&)[8], const int & ) // Result: Values: 28.2 Ulya Yuruk, Istanbul 1000011100000111 Uskudar 1995 */ } * Örnek 4.5, #include #include constexpr int sum(std::same_as auto... args) { int result{}; ((result += args), ...); return result; } int main() { constexpr auto x = sum(17, 9, 1993, 10, 11, 1995); // 4035 } * Örnek 4.6.0, #include template void print_impl(T x) { std::cout << "print_impl(" << x << ")\n"; } template void print(Ts... args) { (print_impl(args), ...); } int main() { // OUTPUT //print_impl(1) //print_impl(2) //print_impl(3) //print_impl(4) //print_impl(5) print(1, 2, 3, 4, 5); } * Örnek 4.6.1, #include #include #include #include // Check if "T" has ".begin()" and ".end()" functions. template concept hasIterator = requires (T a) { a.begin(); a.end(); }; template void impl(const T& x) { if constexpr (hasIterator) { std::cout << "["; for (auto& elem : x) std::cout << elem << ' '; std::cout << "]"; } else if constexpr (std::is_pointer_v) std::cout << *x; else std::cout << x; std::cout << ' '; } void print(const auto&... args) { /* # Function Call # * (impl(p5), (impl(p4), (impl(p3), (impl(p2), impl(p1))))) */ ((impl(args)), ...); } int main() { std::array arr{ 9, 8, 7 }; std::vector vec{ 1.5, 2.0, 2.5, 3.0 }; int* pi{ new int{13} }; print(345, 4.5, arr, vec, pi); // 345 4.5 [9 8 7 ] [1.5 2 2.5 3 ] 13 delete pi; } * Örnek 4.7, #include template void fold_print(First&& f, Args&&... args) { std::cout << f; auto print_with_comma = [](const auto& v) { std::cout << ", " << v; }; (..., print_with_comma(std::forward(args))); std::cout << '\n'; } int main() { fold_print("Ulya", 10, 11, 1995, "Yuruk"); // Ulya, 10, 11, 1995, Yuruk } * Örnek 4.8.0, #include #include template void push_back_right(std::vector& v, Args&&... args) { (v.push_back(std::forward(args)), ...); } template void push_back_left(std::vector& v, Args&&... args) { (..., v.push_back(std::forward(args))); } int main() { std::vector ivec{}; push_back_right(ivec, 10, 11, 1995); for (auto i : ivec) std::cout << i << ' '; // 10 11 1995 std::cout << '\n'; push_back_left(ivec, 17, 9, 1993); for (auto i : ivec) std::cout << i << ' '; // 10 11 1995 17 9 1993 std::cout << '\n'; } * Örnek 4.8.1, #include #include template decltype(auto) push_back(std::vector& vec, Ts&&... args) { (..., vec.push_back(std::forward(args))); return (vec); } int main() { std::vector ivec; push_back(ivec, 10, 11, 1995, 17, 9, 1993).push_back(2024); for (auto i : ivec) std::cout << i << ' '; // 10 11 1995 17 9 1993 2024 } * Örnek 4.9.0, A hash for our custom class. #include #include #include struct Myclass { int mx; double my; std::string mz; }; // Way-I: A hasher for our custom class. template<> struct std::hash { std::size_t operator()(const Myclass& other) { // We defined this function according to our desire, or known algorithms. return std::hash{}(other.mx) / std::hash{}(other.my) + std::hash{}(other.mz); } }; //// Way-II: A hasher class for our custom class. //struct MyclassHasher { // std::size_t operator()(const Myclass& other) { // // We defined this function according to our desire, or known algorithms. // // return std::hash{}(other.mx) / // std::hash{}(other.my) + std::hash{}(other.mz); // } //}; int main() { std::cout << "Hash Value of 31: " << std::hash{}(31) << '\n'; // Hash Value of 31: 3744932218 std::cout << "Hash Value of 3.1: " << std::hash{}(3.1) << '\n'; // Hash Value of 3.1 : 2791122276 std::string name{ "Ulya Yuruk" }; std::cout << "Hash Value of name: " << std::hash{}(name) << '\n'; // Hash Value of name : 891913182 Myclass mx; std::cout << "Hash Value of mx: " << std::hash{}(mx) << '\n'; // Hash Value of mx : 2166136262 } * Örnek 4.9.1, #include #include #include template void hashCombine(std::size_t& seed, const T& val) { seed ^= std::hash()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } template std::size_t combinedHashValue(const Ts&... args) { std::size_t seed = 0; // Initial seed value. (..., hashCombine(seed, args)); // Chain call to hashCombine(). return seed; } struct Myclass { int mx; double my; std::string mz; }; // Way-I: A hasher for our custom class. template<> struct std::hash { std::size_t operator()(const Myclass& other) { // We defined this function according to our desire, or known algorithms. return combinedHashValue(other.mx, other.my, other.mz); } }; //// Way-II: A hasher class for our custom class. //struct MyclassHasher { // std::size_t operator()(const Myclass& other) { // // We defined this function according to our desire, or known algorithms. // return combinedHashValue(other.mx, other.my, other.mz); // } //}; int main() { std::cout << "Hash Value of 31: " << std::hash{}(31) << '\n'; // Hash Value of 31: 3744932218 std::cout << "Hash Value of 3.1: " << std::hash{}(3.1) << '\n'; // Hash Value of 3.1 : 2791122276 std::string name{ "Ulya Yuruk" }; std::cout << "Hash Value of name: " << std::hash{}(name) << '\n'; // Hash Value of name : 891913182 Myclass mx; std::cout << "Hash Value of mx: " << std::hash{}(mx) << '\n'; // Hash Value of mx : 4276989417 } * Örnek 4.9.2, #include #include #include template inline void hash_combine(std::size_t& seed, const T& val) { seed ^= std::hash()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } template inline void hash_val(std::size_t& seed, const T& val) { hash_combine(seed, val); } template inline void hash_val(std::size_t& seed, const T& value, const Ts&... args) { hash_combine(seed, value); hash_val(seed, args...); } template inline std::size_t hash_val(const Ts& ... args){ std::size_t seed = 0; hash_val(seed, args...); return seed; } struct Myclass { int mx; double my; std::string mz; }; // A hasher for our custom class. template<> struct std::hash { std::size_t operator()(const Myclass& other) { // We defined this function according to our desire, or known algorithms. return hash_val(other.mx, other.my, other.mz); } }; int main() { std::cout << "Hash Value of 31: " << std::hash{}(31) << '\n'; // Hash Value of 31: 31 std::cout << "Hash Value of 3.1: " << std::hash{}(3.1) << '\n'; // Hash Value of 3.1: 8546506192830196718 std::string name{ "Ulya Yuruk" }; std::cout << "Hash Value of name: " << std::hash{}(name) << '\n'; // Hash Value of name: 5884387088948685134 Myclass mx; std::cout << "Hash Value of mx: " << std::hash{}(mx) << '\n'; // Hash Value of mx: 6142520555506922560 } * Örnek 4.10, Comparison. #include #include template... Ts> constexpr auto min(const T& a, const T& b, const Ts& ... args) { auto m = a < b ? a : b; if constexpr (sizeof...(args) > 0) { auto cmp = [&](const auto& value){ if (value < m) m = value; }; (..., cmp(args)); } return m; } int main(){ constexpr auto res = min(10, 11, 1995, 17, 9, 1993); // 9 } * Örnek 5.0, Usage of "Binary Fold" #include #include #include template void print(Ts&&... args){ (std::cout << ... << std::forward(args)) << '\n'; } int main() { print( 1, 2, 3, std::string{"Ulya Yuruk"}, std::bitset<8>{28u} ); // 123Ulya Yuruk00011100 } * Örnek 5.1.0, #include template... Ts> constexpr int subtract_v1(int value, Ts... args){ return (value - ... - args); // (((value-p1)-p2)-p3) } template... Ts> constexpr int subtract_v2(int value, Ts... args){ return (args - ... - value); // (p1-(p2-(p3-value))) } int main() { constexpr auto result_v1 = subtract_v1(50, 20, 10, 15); // (((50-20)-10)-15) = 5 std::cout << result_v1 << '\n'; constexpr auto result_v2 = subtract_v2(50, 20, 10, 15); // (20-(10-(15-50))) = -25 std::cout << result_v2 << '\n'; } * Örnek 5.1.1, #include constexpr int subtract_v1(int value, std::same_as auto... args){ return (value - ... - args); // ((((value-p1)-p2)-p3) } constexpr int subtract_v2(int value, std::same_as auto... args){ return (args - ... - value); // (p1-(p2-(p3-value))) } int main() { constexpr auto result_v1 = subtract_v1(50, 20, 10, 15); // (((50-20)-10)-15) = 5 std::cout << result_v1 << '\n'; constexpr auto result_v2 = subtract_v2(50, 20, 10, 15); // (20-(10-(15-50))) = -25 std::cout << result_v2 << '\n'; } * Örnek 5.2, #include // Way - I template constexpr auto sum(Ts&&... args){ return (0 + ... + std::forward(args)); } // Way - II // template // constexpr auto summ(const T& base_value = 0, Ts&&... args){ // return (base_value + ... + std::forward(args)); // } // Way - III // template // constexpr auto summ(const T& base_value = 0, std::same_asauto&&... args){ // return (base_value + ... + std::forward(args)); // } int main() { constexpr auto x1 = sum(1, 3, 5, 7, 9); // 25 constexpr auto x2 = sum(1, 3, 5, 7); // 16 constexpr auto x3 = sum(1, 3, 5); // 9 constexpr auto x4 = sum(1, 3); // 4 constexpr auto x5 = sum(1); // 1 constexpr auto x6 = sum(); // 0 } Şimdi de "Fold Expression" a ilişkin "idiom" lara bir göz atalım: >>>> Parametre paketindeki ilk argümanı elde etmek. * Örnek 1, #include #include #include template auto get_first_element(Ts... args) { std::common_type_t result; // ---> (1) : std::common_type_t result; // ---> (2) : std::common_type_t result; ((result = args, true) || ...); // ---> (1) : ((result = p1, true) || (result = p2, true)) // "(result = p1, true)" ifadesi, paketteki ilk elemanı "result" değişkenine // atayacak. Fakat ifadenin geri döndürdüğü değer ise "true" değeri olacak. "true" // olması hasebiyle "||" operatörü kısa devre mekanizmasını işletecek. Dolayısıyla // açılımın geri kalanına bakılmayacak. // ---> (2) : ((result = p1, true) || (result = p2, true)) return result; } int main(){ auto x = get_first_element(2, 4.5); // ---> (1) auto y = get_first_element(1995, 2005); // --->(2) } * Örnek 2, "std::tuple" kullanarak. #include #include template auto get_first_element(Ts... args) { // std::tuple my_tuple{args...}; std::tuple my_tuple{args...}; return std::get<0>(my_tuple); } int main(){ auto x = get_first_element(2, 4.5, "Ahmet", 17, 1995, 4654.789); // ---> (1) std::cout << "x: " << x << '\n'; // x: 2 } >>>> Parametre paketindeki son argümanı elde etmek. * Örnek 1, #include template auto get_first_element(Ts... args) { return (args, ...); } int main(){ auto x = get_first_element(2, 4.5); // ---> (1) auto y = get_first_element(1995, 2005); // --->(2) } * Örnek 2, "std::tuple" kullanarak. #include #include template auto get_last_element(Ts... args) { // std::tuple my_tuple{args...}; std::tuple my_tuple{args...}; return std::get(my_tuple); } int main(){ auto z = get_last_element(199.5, 20.05, "Ulya", 789, 987u, "Y"); std::cout << "z: " << z << '\n'; // z: Y } >>>> Parametre paketindeki elemanları ters sırayla bir fonksiyona göndermek. * Örnek 1, #include void bar(int x) { std::cout << x << ' '; } void foo(auto... args){ int dummy; (dummy = ... = (bar(args), 0)); // # Function Call # /* Step - I: (((dummy = bar(p1), 0) = bar(p2), 0) = bar(p3), 0); Burada "=" operatöründen dolayı ilk olarak "bar(p3), 0)" ifadesi çağrılacak. Bu ifadenin geri dönüş değeri "0" olacak, çünkü "," operatöründen dolayı. ... */ } int main(){ foo(0, 1, 2); // 2 1 0 } >>>> Uygun şartları sağlayanların toplam adedini bulma: * Örnek 1, #include #include constexpr bool pred(int x) { return x % 3 == 0; } constexpr std::size_t pred_count(auto... args){ return (std::size_t{} + ... + (static_cast(pred(args)))); } int main(){ std::cout << pred_count(2, 18, 1, 3, 9, 0, 10, 12) << '\n'; // 5 } >>>> En küçük olanını bulma: * Örnek 1, #include #include auto fold_min(auto... args) { auto min = (args, ...); // Paketteki son öğe en küçük olarak seçildi. ((args < min ? min = args : 0), ...); // Daha sonra yukarıdaki ifade şu şekilde açılacak; // Buradaki "(args < min ? min = args : 0)" ifadesinin // sonucu; // if "args < min" is true, then "min = args". // else, then "0". return min; } int main(){ std::cout << fold_min(10, 11, 1995, 17, 9, 1993, 24, 8, 1995) << '\n'; // 8 } >>>> "Multi Lambda Idiom" : * Örnek 1, #include #include #include template struct MultiLambda : L... { // Parametre paketindekiler, taban sınıf olarak // ele alınacak; yani "multiple inheritence". using L::operator()...; // Taban sınıfların ".operator()" fonksiyonları // bu sınıf içerisinde görülür hale getirildi. // Aksi halde taban sınıfların hepsinde bu isim // aynı anda bulunacağından, "ambiguity" oluşacaktı. }; int main(){ // "MultiLambda" bir sınıf şablonu olduğundan, // şablon parametrelerini bildirmeliyiz. Fakat // "CTAD" devreye girdiğinden, gerek kalmaadı. // Argüman olarak "lambda" ifadeleri kullandığımızdan, // taban sınıf olarak bu ifadelere ilişkin "closure-type" // türler kullanılacak. Taban sınıfların hepsinde de // ".operator()()" olduğunu da unutmayalım. constexpr MultiLambda action{ [](int i){ std::cout << i << '\n'; }, [](double d){ std::cout << d << '\n'; }, [](bool b){ std::cout << std::boolalpha << b << '\n'; }, [](std::string s){ std::cout << s << '\n'; } }; action(12); // 12 // Burada ise parametresi "int" olan ".operator()" // fonksiyonu çağrılacaktır("Function Overload Resolution"). using namespace std::string_literals; std::tuple t(1, true, "Ulya Yuruk"s, 3.0); std::apply( [action](auto... v){ (action(v), ...);}, t ); // "apply" fonksiyonu argüman olarak bir "callable" ve bir // "std::tuple" nesnesi alır. "std::tuple" içerisindeki öğeleri // ilgili "callable" a gönderir. // # OUTPUT # // 1 // true // Ulya Yuruk // 3 } Şimdi de "Fold Expression" a ilişkin "utility" başlık dosyasındaki "Template Meta Programming" kavramına ilişkin araçlara değinelim. Bu araçlar C++14'e kadar uzanmaktadır. >>>> "integer_sequence" : Bir sınıf şablonudur. Birinci parametresi "Type Template Parameter", ikinci parametresi ise "non-type parameter pack". * Örnek 1, Temsili İmplementasonu: #include #include void foo(auto) {} template struct IntegerSequence { static constexpr std::size_t size = sizeof...(Vals); // Paketteki öğe sayısı. using ValueType = T; }; int main(){ IntegerSequence myIS; std::cout << myIS.size << '\n'; // 5 IntegerSequence::ValueType myIS_ValueType; std::cout << myIS_ValueType << '\n'; // 0 // Error: "IntegerSequence" ifadesi // bir tür belirttiği için sadece tür belirtebildiğim yerlerde kullanabilirim. // foo(IntegerSequence); } * Örnek 2, #include #include template void print_sequence(std::integer_sequence seq) { std::cout << "Types in the sequence consist of <" << typeid(decltype(seq)::value_type).name() << ">\n"; std::cout << "[" << seq.size() << "]: "; ((std::cout << vals << ' '), ...); } int main(){ print_sequence(std::integer_sequence{}); // // [6]: 10 11 1995 17 9 1993 } Diğer yandan bu sınıfa ilişkin tür eş isimleri de bildirilmiştir. Bunlardan sırasıyla "index_sequence", "make_integer_sequence ", "make_index_sequence " ve "index_sequence_for" isimleridir. * Örnek 1.0, "index_sequence" in Temsili İmplementasonu: Burada artık tür bilgisini belirtmiyoruz. #include #include template struct IntegerSequence { static constexpr std::size_t size = sizeof...(Vals); // Paketteki öğe sayısı. using ValueType = T; }; template using IndexSequence = IntegerSequence; int main(){ IndexSequence<1, 2, 3, 4, 5> myIS; std::cout << myIS.size << '\n'; // 5 IndexSequence<1, 2, 3, 4, 5, 7, 30, 1995>::ValueType myIS_ValueType; std::cout << myIS_ValueType << '\n'; // 0 } * Örnek 1.1, #include #include int main(){ std::index_sequence<1, 2, 3, 4, 5> myIS; std::cout << myIS.size() << '\n'; // 5 std::index_sequence<1, 2, 3, 4, 5, 7, 30, 1995>::value_type myIS_ValueType; std::cout << myIS_ValueType << '\n'; // 0 } * Örnek 1.2, #include #include template void print_sequence(std::integer_sequence seq) { std::cout << "[" << seq.size() << "]: "; ((std::cout << vals << ' '), ...); } int main(){ std::index_sequence<10, 2, 33, 4, 55> myIS; print_sequence(myIS); // [5]: 10 2 33 4 55 0 std::index_sequence<1, 2, 3, 4, 5, 7, 30, 1995>::value_type myIS_ValueType; std::cout << myIS_ValueType << '\n'; // 0 } * Örnek 2.0, "make_integer_sequence": Yine tür bilgisidir. Tür bilgisinin kullanıldığı yerlerde kullanılır. #include #include #include template void print_sequence(std::integer_sequence seq) { std::cout << "[" << seq.size() << "]: "; ((std::cout << vals << ' '), ...); } int main(){ // Holds static_assert( std::is_same_v, std::integer_sequence> ); // [7]: 0 1 2 3 4 5 6 print_sequence(std::make_integer_sequence{}); } * Örnek 3, "make_index_sequence" : Bu da "make_integer_sequence" in tür isminin yazılmadığı versiyonudur. #include #include #include template void print_sequence(std::integer_sequence seq) { std::cout << "[" << seq.size() << "]: "; ((std::cout << vals << ' '), ...); } int main(){ // Holds static_assert( std::is_same_v, std::integer_sequence> ); // [10]: 0 1 2 3 4 5 6 7 8 9 print_sequence(std::make_index_sequence<10>{}); } * Örnek 4, "index_sequence_for" : Farklı türleri kullanarak oluşturulur. #include #include template void print_sequence(std::integer_sequence seq) { std::cout << "[" << seq.size() << "]: "; ((std::cout << vals << ' '), ...); } int main(){ print_sequence(std::index_sequence_for{}); // [3]: 0 1 2 } Özetle burada amaç, sekanslara tür eş isim oluşturmaktır. Şimdi de bu tür eş isimlerinden faydalanarak yapabileceklerimize değinelim; * Örnek 1.0, "std::tuple" içerisindekileri bir çıkış akımına yazdırmak: #include #include #include #include template void print_tuple_impl(std::basic_ostream& os, const Tuple& t, std::index_sequence) { // --->(2) ((os << (Is == 0 ? "" : ", ") << std::get(t)), ...); // Yukarıdaki pattern: "(os << (Is == 0 ? "" : ", ") << std::get(t))". // İlgili ifade aşağıdaki gibi olacaktır: // ((((os << (p1) << std::get(t)), (os << (p2 == 0 ? "" : ", ") << std::get(t))), (os << (p3 == 0 ? "" : ", ") << std::get(t))), (os << (p4 == 0 ? "" : ", ") << std::get(t))) /* ((os << (Is == 0 ? "" : ", ") << std::get(t)), ...); ( ( os << (Is == 0 ? "" : ", ") << std::get(t) ), ... ); */ } // "std::tuple" nesnesini bir "steam" e yazdırır. template auto& operator<<(std::basic_ostream& os, const std::tuple& t) { os << '('; // --->(1) print_tuple_impl(os, t, std::index_sequence_for{}); // "std::index_sequence_for{}" ifadesi aslında // "std::index_sequence<0, 1, 2, 3>" ifadesine açılacak ki // bu da aslında "std::integer_sequence" // ifadesi demektir. Böylelikle bir indeks bilgisi elde etmiş olacağız. return os << ')'; } int main() { std::tuple tp{ 2.3, "necati", 'A', 555 }; // --->(0) std::cout << tp << '\n'; std::cout << std::make_tuple(2, 6, 1.1, "Yuruk"); } * Örnek 1.1, #include #include #include #include template void print_tuple_impl(std::basic_ostream& os, const Tuple& t, std::index_sequence) { ((os << (Is == 0 ? "" : ", ") << std::get(t)), ...); } template auto& operator<<(std::basic_ostream& os, const std::tuple& t) { os << '('; print_tuple_impl(os, t, std::index_sequence_for{}); return os << ')'; } template auto a2t_impl(const Array& a, std::index_sequence) { return std::make_tuple(a[I]...); // return std::make_tuple(a[0], a[1], a[2], a[3]); } // array to tuple conversion: template> auto a2t (const std::array& a) { return a2t_impl(a, Indices{}); // "Indices" is type of "std::index_sequence<0,1,2,3>". } int main() { std::array arr = {1, 2, 3, 4}; auto tp = a2t(arr); static_assert(std::is_same_v>); std::cout << tp << '\n'; // (1, 2, 3, 4) } * Örnek 2, #include #include template void f(Ts&&... args) { ((std::cout << args << ' '), ...); } template void process_impl(const Tuple& t, std::index_sequence) { f(std::get(t)...); } template void process(const Tuple& t) { // Way - I // constexpr auto t_size = std::tuple_size::value; // process_impl( t, std::make_index_sequence() ); // Way - II // process_impl( t, std::make_index_sequence::value>() ); // Way - III process_impl( t, std::make_index_sequence>() ); } int main() { std::tuple tuple(1, 'u', 2.3); process(tuple); // 1 u 2.3 } * Örnek 3, #include #include #include #include namespace detail { template decltype(auto) Apply_impl(F&& f, Tuple&& tpl, std::index_sequence) { return std::forward(f)( // This is the pattern. No Fold Expression, only Pack Expansion std::get(std::forward(tpl))... ); } } template decltype(auto) Apply(F&& f, Tuple&& tpl) { // Forwarding Reference return detail::Apply_impl( std::forward(f), std::forward(tpl), std::make_index_sequence>>{} ); } int f(int i, char c, double d) { std::cout << i << ' ' << c << ' ' << d << '\n'; return 1; } int main() { auto tp = std::make_tuple(42, 'x', 3.14); int ret = Apply(f, tp); // 42 x 3.14 std::cout << ret << '\n'; // 1 } * Örnek 4, #include #include #include #include #include #include namespace detail { template auto make_array_impl(const T& value, std::index_sequence) { return std::array{ (I,value)... }; // Not Fold Expression, but Pack Expansion // "sizeof...(I)" : Number of elements in the parameter pack. // Pattern => (I,value)... // "std::array{ (I,value)... };" will expand to: // "std::array{ (0,value), (1,value), (2,value), (3,value), (4,value) };" // Then the "," operator will yield "value". So, the next'd be: // "std::array{ 31, 31, 31, 31, 31 };" } } template std::array make_array(const T& value) { return detail::make_array_impl(value, std::make_index_sequence()); } int main() { auto arr = make_array<5>(31); for (const auto& i : arr) std::cout << i << '\n'; std::cout << '\n'; } * Örnek 5, #include #include #include #include template decltype(auto) tuple_subset(const Tuple& t, std::index_sequence) { return std::make_tuple(std::get(t)...); // Pattern: "std::get(t)" // return std::make_tuple( // std::get<0>(t), std::get<2>(t), std::get<3>(t) // ); } int main() { auto tp1 = std::make_tuple(11, 1.1, "Onbir", 111u, '1'); auto tp2 = tuple_subset(tp1, std::index_sequence<0, 2, 3>{}); static_assert(std::is_same_v>); std::cout << '<' << std::get<0>(tp2) << ',' << std::get<1>(tp2) << ',' << std::get<2>(tp2) << ">\n"; // <11,Onbir,111> } >> Şimdi de derleyicilerin bir şablon karşısındaki tutumlarını inceleyelim; >>> "Specialization" : Derleyicinin bir şablonu açması, yani onu "instantiate" etmesi demektir. Dolayısıyla bir şablonun bir tür(ler)e göre açılımını kastederken hem "Specialization" terimini hem de "Instantiation" terimini kullanabiliriz. Ancak buradaki "Specialization" kelimesini "Explicit(Full) Specialization" veya "Partial Specialization" kelimelerindeki "Specialization" kelimesi ile birbirine karıştırmayalım. Bir tanesi bir şablonun bir tür nezdindeki açılımını kastederken, diğeri ise bir şablonun özelleştirilmesine ilişkindir. Diğer yandan derleyici bir şablonu açarken, yani o şablondan bir "Specialization" üretirken, yani o şablonu "Instantiate" ederken, şu aşamalardan geçmektedir; >>>> "Name-Lookup" Süreci: >>>>> "Point of Instantiation": >>>>>> "Dependant Name" : Bu kavram o ismin, isim arama sırasında, şablon parametresine ilişkin olup olmadığını belirtmek için kullanılır. * Örnek 1, template void foo(T x) { bar(x); // Yukarıdaki fonksiyon çağrısı, "foo" çağrısı yapılmasa bile, // sentaks hatasına yol açmaz çünkü "x" ismi şablon parametresine // ilişkin bir isimdir. İşte "x" için "Dependant Name" kavramı // kullanılır. bar(12); // Ancak bu çağrı sentaks hatasına yol AÇACAKTIR. } * Örnek 2, #include template void foo(T x) { bar(x); // "x" is a dependent name. } class Neco{}; void bar(Neco) { std::cout << "bar(Neco)\n"; } void bar(int) { std::cout << "bar(int)\n"; } int main() { Neco mx; // Output => bar(Neco) foo(mx); // Because of "Point of Instantiation" // error: ‘bar’ was not declared in this scope, // and no declarations were found by argument-dependent lookup // at the point of instantiation. // note: ‘void bar(int)’ declared here, later in the translation unit // foo(10); } >>>> "Template-ID" Belirlenmesi: Burada ise şablon parametrelerinin hangi türler olduğununun saptandığı evredir. Bu belirleme sürecinde ise karşımıza üç farklı yol çıkmaktadır. "Explicit" olarak bizlerin türlerin hangi tür olduğunu belirtmesiyle, "Default Template Argument" kullanılması, fonksiyon çağrılarında kullanılan argüman(lar)dan hareketle tür çıkarımı yapılması("Template Argument Deduction"). * Örnek 1, "Explicit" olarak bizlerin belirtmesi: template void foo() {} int main() { foo(); } * Örnek 2, "Default Template Argument" kullanılması: template void foo() {} int main() { foo(); } * Örnek 3, "Template Argument Deduction": template void foo(T x) {} int main() { foo('a'); } >>>> "Substitution" Evresi: Artık fonksiyonun imzasının kesinleştiği evredir. Şablon parametrelerinin yerlerine yerleştirildiği evredir. Artık fonksiyonun parametrik yapısının tamamen belirlendiği evredir. Örneğin, geri dönüş türü belirlenir. Tabii bu yerleştirme aşamasında ilk olarak "explicit" olarak belirttiğimiz türler yerleştirilir, daha sonra diğerleri. Eğer bu aşamada geçersiz bir durum oluşursa, mesela bir türün olmaması gibi, derleyici doğrudan sentaks hatası vermek yerine, "Substitution Failure is not an Error", ilgili fonksiyonu "Function Overload Resolution" evresinde kullanılacak kümeden ÇIKARTIYOR. İşte "concepts" kavramı hayatımıza girmeden evvel şablonların kısıtlanması "Substitution Failure is not an Error(SFINAE)" yöntemi ile yapılmaktaydı. * Örnek 1, template typename T::value_type foo(T x) {} int main() { foo(12); // no matching function for call to ‘foo(char)’ /* * İlk olarak "foo" ismi arandı ve şablon ismi olduğu öğrenildi. ("Name Lookup") * Daha sonra fonksiyon çağrısındaki ifadeden yola çıkılarak "T" için "int" türü geldiği * anlaşıldı. ("Template-ID" Belirlenmesi) * Fonksiyonun geri dönüş değeri için "int" türünün "value_type" türü olduğu belirlendi * fakat böyle bir şey söz konusu olmadığından, ilgili fonksiyon yukarıda bahsedilen * kümeden çıkartıldı. ("Substitution") * Kümede başka bir fonksiyon olmadığından, programımız derleme hatası vermiştir. Eğer uygun * başka fonksiyonlar kümede olsaydı, "Function Overlaod Resolution" işlemi uygulanacaktı. */ } Diğer yandan "Instantiation" iki farklı biçimde yapılabilmektedir; "Explicit Instantiation", "Implicit Instantiation". Şimdi de bunları inceleyelim: >>>> "Explicit Instantiation": Derleyiciye açıkça, "Bu şablonu, bu şablon argümanıyla, aç" demektir. Kendi bünyesinde ayrı sentaks kuralları barındırır. İlerleyen vakitlerde değinilecektir. Bu yaklaşımı "Explicit Specialization" / "Partial Specialization" ile KARIŞTIRMAMALIYIZ. Bir tanesi şablonu açmak için kullanılan yöntemken, diğer bir şablonu özelleştirme yöntem(ler)idir. Sadece fonksiyon şablonlarına has değil; diğer şablon tipleri için de kullanılabilir. "Manuel Instantiate Talimatı" olarak da geçer. * Örnek 1.0, Yapılış biçimi aşağıdaki gibidir; #include template void foo(T x) { std::cout << typeid(T).name() << '\n'; } template void foo(int); // An Explicit Specialization for "T" is "int". template void foo<>(double); // Another Explicit Specialization for "T" is "double". template void foo(char); // Another Explicit Specialization for "T" is "char". int main() { //... } * Örnek 1.1, #include template struct Neco {}; template struct Neco; // An Explicit Specialization for "T" is "int". int main() { //... } * Örnek 1.2, #include template struct Neco { void foo() {} }; template void Neco::foo(); // An Explicit Specialization for "T" is "float". int main() { //... } * Örnek 2, // ========== file.hpp template void foo(); // A declaration. Preventing instantiation. // ========== file.tpp (".tpp" is another convension of ".hpp") #include "file.hpp" template void foo() { // A definition. // ... => // Implementation. } // ========== file.cpp #include "file.tpp " template void foo(); // An Explicit Instantiation. "T" will be "int". Diğer yandan şunu da hatırlatmakta fayda vardır. Bir başlık dosyasının "#include" edildiği her bir "Translation Unit", derlemeye tabii tutulmaktadır. Örneğin "file.hpp" başlık dosyası 100 farklı dosyada "#include" edilmiş olsun. Derleme aşamasında her bir dosya derlemeye tabii tutulacağından, iş bu başlık dosyası da 100 defa derlenmiş olacaktır. * Örnek 1.0.0, Aşağıdaki örnekte de görüleceği üzere "tpl_func()" iki farklı "object file" içerisinde de bulunmaktadır. // ----> (0), tpl_func.h: "tpl_func" fonksiyonunun tanımı yer almaktadır. template void tpl_func() { //... } // ----> (1), file_x.cpp: Bu dosyanın derlenmesiyle bir "object file" oluşur. İsmi "file_x.o" olsun. #include "tpl_func.h" void fx() { //... tpl_func(); // Bu noktada fonksiyon çağrısı yapıldığından "Instantiation" yapılacak. } /* // ----> (1), // "file_x.o" 000000 W void tpl_func() 000000 T fx(); */ // ----> (2), file_y.cpp: Bu dosyanın derlenmesiyle ayrı bir "object file" oluşur. İsmi "file_y.o" olsun. #include "tpl_func.h" void fy() { //... tpl_func(); // Bu noktada fonksiyon çağrısı yapıldığından "Instantiation" yapılacak. } /* ----> (2), // "file_y.o" 000000 W void tpl_func() 000000 T fy(); */ // ----> (3), // İlgili "object" dosyaları incelediğimizde, ayrı ayrı "tpl_func()" // olduğunu görürüz. Yani ilgili fonksiyonun kodu iki defa derlenmiş. Buradaki // "W" ifadesi "Weak Reference" anlamındadır. Dolayısıyla link aşamasında // bunlardan sadece birisi alınmaktadır. Eğer 100 adet dosya içerisinde "tpl_func()" // olsaydı, ilgili fonksiyonun kodu 100 defa derlenecektir. Bu da derleme zamanına ilişkin // yük oluşturmaktadır. * Örnek 1.0.1, Aşağıdaki örnekte ise derleyiciye o modül içerisinde "Instantiate" YAPTIRMAMA talimatı vermiş oluyoruz. // tpl_func.h template void tpl_func() { //... } // file_x.cpp // #include "tpl_func.h" void fx() { //... tpl_func(); // Bu noktada fonksiyon çağrısı yapıldığından "Instantiation" yapılacak. } // file_y.cpp // #include "tpl_func.h" extern template void tpl_func(); // This is NOT "Explicit Instantiation", but "Extern Template Declaration". // Derleyiciye "Instantiation" YAPMAMA TALİMATI VERDİK. Bu modül içerisinde // derleyici "Instantiation" YAPMAYACAKTIR. Artık "Instantiation", sadece "file_x.cpp" // içerisindeki "tpl_func()" çağrıdan dolayı yapılacaktır. void fy() { //... tpl_func(); } // file_x.o 000000 W void tpl_func() 000000 T fx(); // file_y.o 000000 T fx(); // Görüleceği üzere sadece bir tane "tpl_func()" olduğunu görüyoruz. İşte // "file_y.cpp" içerisindeki "Extern Template Declaration" ı başka modüllerde de // yapsaydık, o modüllerdeki "tpl_func()" çağrıları sonucunda "Instantiate" // yapılmayacaktır. Böylelikle derleme zamanına ilişkin yükü azaltmış oluyoruz. int main() { } Şimdi bu iki örneği özetlersek; -> "extern template void tpl_func();" bildirimi ile derleyiciye diyoruz ki bu bildirimi gördüğün "translation unit" de eğer "tpl_func" çağrısı yapılmışsa, bu çağrı için "Instantiation" YAPMA, yani senin o fonksiyonun kodunu derlemeni İSTEMİYORUM. Dolayısıyla ilgili bildirimin mevcut olmadığı ve "tpl_func" çağrısının yapıldığı "translation unit" (ler)de "Instantiation" yapılacaktır. Peki bu durumda biz şöyle bir şey yapsak nasıl olur? -> Bir başlık dosyasına şablonu yerleştiririm. Eğer o şablonun belirli tür(ler)den açılımlarını kullanacaksam, yani o baştan belliyse, kaynak dosyaların hepsine(bir tanesi hariç) "extern template void tpl_func();" bildirimini eklerim. O hariç olan kaynak dosyaya da "Manuel Instantiate" talimatını yerleştiririm. Bir diğer deyişle "extern template void tpl_func();" bildirimini başlık dosyasına eklerim. Bu başlık dosyasını "#include" eden bütün ".cpp" dosyaları da haliyle bu bildirimi de eklemiş olacaklar. Sonra, harici bir tane ".cpp" dosyası alıp, içerisine "Manuel Instantiate" talimatı yerleştiririm. Günün sonunda toplamda "n" tane dosya "#include" işlemi gerçekleştirmişse, "n" tanesinde "extern template void tpl_func();" bildirimi, o harici olan bir tanesinde de "Manuel Instantiate" bildirimi olmuş olur. Dolayısıyla sadece bir tane dosyada ilgili fonksiyonun kodu derlenmiş olacak, diğer dosyalardaki çağrılar ise derlenmiş olanı referans alacaklar. Şimdi burada da şöyle bir dezavantaj açığa çıkmaktadır; derleyici sadece bir defa derleme yapacağından, fonksiyonun kodu sadece bir defa açılacaktır. Dolayısıyla derleyici, diğer dosyalar ile birleştirip de optimizasyon yapma olanağı ORTADAN KALKMIŞ OLDU. * Örnek 1, BURADAKİ KODU TEKRAR İNCELE; NECATİ ERGİN GITHUB. //////////////////////// // tpl_func.h template void tpl_func { //... } extern template void tpl_func(); //////////////////////// // tpl_func.cpp #include "tpl_func.h" // With this directive, the following one is // also being included; // extern template void tpl_func(); template void tpl_func(); // "Manuel Instantiation" //////////////////////// // file_x.cpp #include "tpl_func.h" // With this directive, the following one is // also being included; // extern template void tpl_func(); void fx() { //... tpl_func(); } //////////////////////// // file_y.cpp #include "tpl_func.h" // With this directive, the following one is // also being included; // extern template void tpl_func(); void fy() { //.. tpl_func(); } -> Pekiyi yukarıdaki gibi başlık dosyasına "extern template void tpl_func();" yerleştirmek yerine, sadece belli kaynak dosyalarına bu bildirimi yerleştirsek nasıl olur? Açıkçası böyle yapmak çok risklidir. Çünkü "Instantiate" yapılacak kaynak dosyadaki fonksiyon çağrısı "inline" olarak ele alınırsa, derlenmiş dosyada "tpl_func" için bir kod YER ALMAYACAKTIR. Dolayısıyla "extern template void tpl_func();" bildirimlerinin mevcut olduğu diğer bütün modüller, derleme sekansı bittikten sonra, tanımı olmayan bir fonksiyonu referans almış olacaklar. Yani "Dangling Reference" problemi. Yani link aşamasında patlayacağız. Dolayısıyla bizlerin ilgili fonksiyonu "inline" OLARAK ELE ALINMAMASINI BİR ŞEKİLDE SAĞLAMALIYIZ. Maalesef "inline" olarak ele almayı engelleyecek bir bildirim şekli standart DEĞİL. Tamamen derleyicilerin ayarlarıyla ilgili. Aşağıdaki örnekleri inceleyelim; * Örnek 1.0, Aşağıdaki programda "linker" program hata verebilir. //////////////////////// // tpl_func.h template void tpl_func() { //... } //////////////////////// // file_x.cpp #include "tpl_func.h" void fx() { //... // Gcc ve Cland derleyicilerinde optimizasyon seçeneği // "-O2" seçilirse "linker" program, aşağıdaki fonksiyon // çağrısına ilişkin ilgili fonksiyonun tanımsız olduğuna // dair, hata mesajı verebilir. tpl_func(); } //////////////////////// // file_y.cpp #include "tpl_func.h" extern template void tpl_func(); void fy() { //.. tpl_func(); } //////////////////////// // file_x.o // 000000 T fx(); // file_y.o // 000000 T fy(); // "Object" dosyalardan da görüleceği üzere, // "file_x.cpp" içerisindeki "tpl_func();" // çağrısı "inline" olarak ele alınmıştır. * Örnek 1.1, İşte "tpl_func.h" dosyasındaki fonksiyon tanımını aşağıdaki biçimde yaparsak, "inline" olarak ele alınmayacaktır. Fakat bu yöntem derleyiciye bağlı bir yöntemdir. //////////////////////// // tpl_func.h template void __attribute__((noinline)) tpl_func { //... } //////////////////////// // file_x.cpp #include "tpl_func.h" void fx() { //... // Gcc ve Cland derleyicilerinde optimizasyon seçeneği // "-O2" seçilirse "linker" program, aşağıdaki fonksiyon // çağrısına ilişkin ilgili fonksiyonun tanımsız olduğuna // dair, hata mesajı verebilir. tpl_func(); } //////////////////////// // file_y.cpp #include "tpl_func.h" extern template void tpl_func(); void fy() { //.. tpl_func(); } //////////////////////// // file_x.o // 000000 W void tpl_func() // 000000 T fx(); // file_y.o // 000000 T fy(); Dolayısıyla günün sonunda ilgili başlık dosyasına "extern template void tpl_func();" bildirimini eklemek makul bir yöntem. Ek olarak iş bu yöntem, üçüncü parti kütüphanelere de uygulanabilir. Şöyleki; * Örnek 1, //////////////////////// // thid_party.h template void tpl_func { //... } //////////////////////// // tpl_func.h #include "thid_party.h" extern template void tpl_func(); //////////////////////// // tpl_func.cpp #include "tpl_func.h" // With this directive, the following one is // also being included; // extern template void tpl_func(); template void tpl_func(); // "Manuel Instantiation" //////////////////////// // file_x.cpp #include "tpl_func.h" // With this directive, the following one is // also being included; // extern template void tpl_func(); void fx() { //... tpl_func(); } //////////////////////// // file_y.cpp #include "tpl_func.h" // With this directive, the following one is // also being included; // extern template void tpl_func(); void fy() { //.. tpl_func(); } Buraya kadar anlatılanları sınıf şablonları için de uygulayabiliriz. Şöyleki; * Örnek 1, //////////////////////// // tClass.h template class tClass { //... }; //////////////////////// // file_x.cpp #include "tClass.h" void fx() { //... tClass bc; } //////////////////////// // file_y.cpp #include "tClass.h" extern template class tClass; void fy() { //... tClass bc; } Hakeza standart kütüphanedeki öğeler ise benzer yöntemi kullanmaktadır. Şöyleki; * Örnek 1, namespace std{ template< typename charT, typename traits = char_traits, typename Allocator = allocator > class basic_string { //... }; extern template class basic_string; extern template class basic_string; } >>>> "Implicit Instantiation": Fonksiyon çağrısı yapıldığı için, ya da belirli bir sınıf türü kullanıldığı için, derleyicinin "Instantiation" yapmaya zorlanmasıdır. Aslında bu zamana kadar şablonları kullandığımız yerdeki kullanım biçimimiz, "Implicit Instantiation" yaklaşımıdır. Şimdi de şu soruya yanıt arayalım; hangi durumlarda "Instantiation" gerekiyor, hangi durumlarda gerekmiyor. Çünkü derleyici(ler) her durumda bir "Instantiation" YAPMAMAKTADIR. Bazı koşulların da sağlanması gerekmektedir. Aşağıdaki örnekleri inceleyelim; * Örnek 1.0.0, Aşağıdaki örnekte "p" için bir "instantiation" oluşmayacak. #include template struct Neco{ Neco() { std::cout << "Neco\n"; } void foo(){ std::cout << "foo\n"; } void bar(){ std::cout << "bar\n"; } void baz(){ std::cout << "baz\n"; } }; int main() { // No instantiation at all. Neco* p{}; // instantiation for only "foo" func. No instantiation for other func.(s) p->foo(); // Output: foo //... // instantiation for only "baz" func. No instantiation for "bar" func. p->baz(); // Output: baz } * Örnek 1.0.1, "cppinsights.com" çıktısı: #include template struct Neco { inline Neco() { std::operator<<(std::cout, "Neco\n"); } inline void foo() { std::operator<<(std::cout, "foo\n"); } inline void bar() { std::operator<<(std::cout, "bar\n"); } inline void baz() { std::operator<<(std::cout, "baz\n"); } }; /* First instantiated from: insights.cpp:17 */ #ifdef INSIGHTS_USE_TEMPLATE template<> struct Neco { inline Neco(); inline void foo() { std::operator<<(std::cout, "foo\n"); } inline void bar(); inline void baz(); }; #endif int main() { Neco * p = {}; p->foo(); return 0; } * Örnek 2, "static" veri elemanları "main" fonksiyon çağrısından evvel hayata geldiği için, yine burada ilgili şablon "instantiate" edilecektir. #include template struct Neco{ static T ms; }; template T Neco::ms = 0; int main() { Neco n0; std::cout << n0.ms << '\n'; // 0 Neco n1; std::cout << n1.ms << '\n'; // 0 Neco n2; std::cout << n2.ms << '\n'; // 0 n2.ms = 2.3; std::cout << n0.ms << '\n'; // 0 std::cout << n1.ms << '\n'; // 2.3 std::cout << n2.ms << '\n'; // 2.3 } * Örnek 3, Aşağıdaki örneği inceleyelim; template class C; // #1: Declaration Only C* p = 0; // #2: OK. Since a definition for C is not needed. template class C { public: void f(); // #3: Member Declaration }; // #4: Class Template Definiton is now completed. void g(C& c) { // #5: OK. Class Template Definiton is NOT needed. c.f(); // #6.0: Require Class Template Definiton. A definition // is needed for "C::f()" in this translation unit. C* ptr = new C; // #6.1: Require Class Template Definiton. A definition // is needed for "C::f()" in this translation unit. } template void C::f() { // Required definition needed @ step 6. //... } int main() { } * Örnek 4.0, template struct Nec{ using type = typename T::value_type; }; int main() { // No Instantiation: Açılım olmadığı için, // "Nec" sınıf içerisindeki "using" bildirimi // sentaks hatası oluşturmayacak. Nec* p{}; } * Örnek 4.1, template struct Nec{ using type = typename T::value_type; }; int main() { // In instantiation of ‘struct Nec’: // ‘int’ is not a class, struct, or union type Nec p{}; // Böylesi bir tanımlama şablon açılımı gerektirdiği için, // ilgili "using" bildirimi sentaks hatası oluşturdu. } * Örnek 5, template T foo(T x) { using type = typename T::value_type; return x*x; } int main() { // No Instantiation will happen here. decltype(foo(12)) x = 5; // Çünkü burada derleyici, "foo" fonksiyonunun // imzasını oluşturması gerekmektedir. Dolayısıyla // şablonu açmadı. } >> Diğer yandan parametre paketlerine ilişkin "C++20 Idioms for parameter packs by David Mazières" isimli kaynaktan da fayda sağlayabiliriz.