> "concepts" : Buradaki amaç şablon kodlarını "constraint" etmektir. Yani şablon argümanını belirli bir miktarda kısıtlamak için kullanılır. Böylelikle yanlış bir şablon argümanı kullanıldığında ya direkt sentaks hatası oluşacak ya ilgili şablonun belli bir kısmı kullanılacak ya da derleyici nokta atışı mesaj vererek hatanın tam olarak nereden kaynaklandığını belirtecektir. Burada kendi kısıtlamalarımızı kendimiz oluşturabildiğimiz gibi, standart kütüphanenin bize hazır olarak verdiği kısıtlamaları da kullanabiliriz. * Örnek 1, #include #include template void foo(T x) { std::cout << "Tam sayi\n"; } template void foo(T x) { std::cout << "Gercek sayi\n"; } void bar(std::integral auto) { std::cout << "Tam sayi\n"; } void bar(std::floating_point auto) { std::cout << "Gercek sayi\n"; } int main() { /* # OUTPUT # Tam sayi Tam sayi Gercek sayi Gercek sayi Tam sayi Tam sayi Gercek sayi Gercek sayi */ foo(23); foo(23L); foo(2.3f); foo(2.3); bar(23); bar(23L); bar(2.3f); bar(2.3); } Diğer yandan C++20 ile "concepts" kavramının ile eklenmesiyle birlikte toplamda beş adet şablon bulunmaktadır. Sınıf şablonu, fonksiyon şablonu, "variable templates", "allias template" ve "concepts". Aynı zamanda da "concept" bir anahtar sözcüktür. * Örnek 1, "concept" olmasaydı, aşağıdaki gibi bir çözüm sergilemek zorundaydı. #include #include template>* = nullptr> void foo(T) { std::cout << "foo\n"; } template std::enable_if_t, T> bar(T) { std::cout << "bar\n"; return true; } template void baz(T, std::enable_if_t>* p = nullptr) { std::cout << "baz\n"; } int main() { /* # OUTPUT # */ foo(340); // OK foo(3.4); // ERROR: no matching function for call to ‘foo(double)’ bar(340); // OK bar(3.4); // ERROR: no matching function for call to ‘bar(double)’ baz(340); // OK baz(3.4); // ERROR: no matching function for call to ‘baz(double)’ } * Örnek 2, "concept" olmasaydı, aşağıdaki sentaks hataları oluşacaktı. #include #include #include template void print(const T& t) { std::cout << t << '\n'; } template void convert_to_int(const T& t) { int x = t; } int main() { /* # OUTPUT # */ print(12); print(1.2); print(std::string{"Merve"}); // ERROR: no match for ‘operator<<’ (operand types are ‘std::ostream’ // {aka ‘std::basic_ostream’} and ‘const std::vector >’) std::vector vec{ 45, 5, 7, 2 }; print(vec); // ERROR: cannot convert ‘const std::__cxx11::basic_string’ to ‘int’ in initialization convert_to_int(std::string{"Merve"}); } Pekiyi bizler nasıl "constraint" ederiz? Burada üç farklı araç karşımıza çıkmaktadır. Bunlar, "requires clause", "requires expression", "named constraints(concept)" araçlarıdır. Bu araçlardan, >> "requires clause" : Genel sentaks şu şekildedir: " requires ("boolean" olarak yorumlanacak "Compile Time Expression") " İki biçimde kullanılır. Bunlar "prefix" ve "trailing" biçimde. >>> "prefix" yönteminde fonksiyonun parametre değişkeninin ismini kullanamayız. Çünkü bu yöntemde, yukarıdaki ifadeyi, fonksiyonun geri dönüş değerinin türünden önce yazmaktayız. * Örnek 1, Aşağıdaki "requires clause" kullanımında "prefix" biçimi uygulanmıştır. #include // "T" türünün "sizeof" değerinin "2" bayttan büyük olması gerekmektedir. template requires (sizeof(T) > 2) void foo(T x) { std::cout << "sizeof x : " << sizeof(x) << '\n'; } int main() { // foo('A'); // ERROR: no matching function for call to ‘foo(char)’ foo(45); // OK : sizeof x : 4 } >>> "trailing" yönteminde ise fonksiyonun parametre değişkeninin ismini kullanabiliriz. Çünkü bu yöntemde yukarıdaki ifadeyi fonksiyonun imzası ile "{" arasındaki kısma yazmaktayız. * Örnek 1, #include // "x" değişkeninin "sizeof" değerinin "4" bayttan büyük olması gerekmektedir. template void foo(T x) requires (sizeof(x) > 4) { std::cout << "sizeof x : " << sizeof(x) << '\n'; } int main() { //foo(45); // ERROR: no matching function for call to ‘foo(int)’ foo(45L); // OK : sizeof x : 8 } Pekala bu iki biçimi de aynı anda kullanabiliriz. * Örnek 1, #include #include template requires std::is_integral_v void foo(T x) requires std::is_signed_v { std::cout << "sizeof x : " << sizeof(x) << '\n'; } int main() { foo(45); foo(4.5); // ERROR: no matching function for call to ‘foo(double)’ foo(45u); // ERROR: no matching function for call to ‘foo(unsigned int)’ } Tabii bu biçimleri böyle aynı anda kullanmak yerine, "logic" operatörleriyle de birleştirerek kullanabiliriz. * Örnek 1, #include #include template requires std::is_integral_v && (sizeof(T) > 2) void foo(T x) { std::cout << "sizeof x : " << sizeof(x) << '\n'; } int main() { foo('A'); // ERROR: no matching function for call to ‘foo(char)’ foo(45); foo(4.5); // ERROR: no matching function for call to ‘foo(double)’ foo(45u); } * Örnek 2.0, #include #include template requires !(sizeof(T) > 2) // ERROR: expression must be enclosed in parentheses void foo(T x) { std::cout << "sizeof x : " << sizeof(x) << '\n'; } int main() { /* * "!" operatörünü kullanmak istiyorsak, "()" içerisinde kullanmalıyız. */ } * Örnek 2.1, #include #include template requires (!(sizeof(T) > 2)) // ERROR: expression must be enclosed in parentheses void foo(T x) { std::cout << "sizeof x : " << sizeof(x) << '\n'; } int main() { foo('a'); foo(65); // ERROR: no matching function for call to ‘foo(int)’ } * Örnek 3, #include #include #include #include template requires (sizeof(T) > 2) && // "requires clause" => "T" türünün "sizeof" değerinin "2" den büyük olması requires { typename T::size_type; } && // "requires expression" => "T" türü "size_type" isminde "nested type" a sahip olması std::input_iterator // "concept" => "input_iterator" konseptinin "T" türüne açılabilir olması class Myclass{ }; int main() { //... } * Örnek 4, #include #include #include template requires std::is_pointer_v || std::is_reference_v class Myclass{ }; int main() { // ERROR: template constraint failure for // ‘template requires (is_pointer_v) || (is_reference_v) class Myclass’ Myclass m1; } * Örnek 5, template // template requires (N > 10) class Neco{ }; int main() { Neco<5> n1; // ERROR: template constraint failure for ‘template requires N > 10 class Neco’ Neco<15> n2; // OK } Buradaki önemli nokta, derleme zamanında "constant expression" olabilmesidir. >> "concept" : İsimlendirilmiş "constraint" lerdir. Tekrar tekrar belirtmek yerine, vermiş olduğumuz ismi kullanıyoruz. Yine derleme zamanında "boolean" değer üretmesi gerekmektedir. * Örnek 1, #include #include #include template concept Integral = std::is_integral_v; // Bu noktada diğer "concept", "requires clause" ve "requires expression" // kullanarak istediğimiz kombinasyonları yapabiliriz. template requires Integral void foo(T) {} template void func(T) {} // Burada fonksiyon şablonu kullanabildiğimiz gibi sınıf şablonu da kullanabilirdik. // Hakeza diğer şablon türlerini de. Buradaki şart, "Integral" şartını sağlamasıdır. // Yani "std::is_integral_v" ifadesinin "true" değer üretmesi gerekiyor. template class Myclass{ }; int main() { foo(12); // OK func(1.2); // ERROR: no matching function for call to ‘func(double)’ (*) func(12); // OK func(1.2); // ERROR: no matching function for call to ‘func(double)’ (*) /* (*) * Burada bizler "CTAT" yararlandık. Özel olarak türü belirtsek bile yine * sentaks hatası olabilirdi. Çünkü buradaki kıstas, yukarıdaki "std::is_integral_v" * ifadesinin "true" değer üretmesidir. */ Myclass m1; // Burada herhangi bir sentaks hatası yoktur. Çünkü "T" ve "U" isminde iki farklı // şablon parametresi vardır. Bura bizim sağlamamız gereken şart ise // "std::is_integral_v && std::is_integral_v" ifadesinin "true" değer // üretmesidir. // ERROR: template constraint failure for ‘template requires (Integral) && (Integral) class Myclass’ Myclass m2; // ERROR: template constraint failure for ‘template requires (Integral) && (Integral) class Myclass’ Myclass m3; } * Örnek 2, #include #include #include template concept Integral = std::is_integral_v; template concept SignedIntegral = Integral && std::is_signed_v; template concept UnsignedIntegral = Integral && !SignedIntegral; int main() { //... } * Örnek 3, #include #include #include template concept as_int = std::integral || std::is_convertible_v; template requires as_int void foo(void) {} struct Neco{}; struct Myclass{ operator int()const; }; int main() { foo(); // OK foo(); // OK foo(); // OK foo(); // OK foo(); // ERROR: no matching function for call to ‘foo()’ foo(); // OK } * Örnek 4, #include #include #include template concept as_int = std::integral || std::is_convertible_v; template class Myclass{ }; struct Nec{}; // Way - I // template // void foo (T x); // Way - II void foo(as_int auto x) {} int main() { //Myclass m1; // ERROR : template constraint failure for ‘template requires as_int class Myclass’ Myclass m_i; // OK foo(15); // OK } * Örnek 5, #include #include #include #include void foo(std::convertible_to auto x) { std::cout << "x: " << x << '\n'; } void foo(std::integral auto x) { std::cout << "x: " << x << '\n'; } // II /* Alternative - I template void foo(T x) { std::cout << "x: " << x << '\n'; } Alternative - II template requires std::integral void foo(T x) { std::cout << "x: " << x << '\n'; } */ int main() { foo(std::string{ "Merve" }); // x: Merve foo(31); // x: 31 foo(3.1); // ERROR: no matching function for call to ‘foo(double)’ } * Örnek 6, #include #include #include #include std::integral auto bar(int x) { return x * 1.1; } // ERROR: deduced return type does not satisfy placeholder constraints int foo() { return 1; } double func() { return 1.1; } int main() { std::integral auto x = foo(); std::integral auto y = func(); // ERROR: deduced initializer does not satisfy placeholder constraints } * Örnek 7, #include #include #include #include #include int main() { // 0 0 0 0 0 std::vector ivec(5); for(std::integral auto x : ivec){ std::cout << x << ' '; } } * Örnek 8, #include #include #include #include #include #include template void func(T x) { if constexpr (std::integral){ std::cout << "Tam sayi turler...\n"; } else if constexpr (std::floating_point){ std::cout << "Gercek sayi turler...\n"; } else { std::cout << "Diger turler...\n"; } } int main() { /* # OUTPUT # Tam sayi turler... Gercek sayi turler... Diger turler... */ func(12); func(1.2); func("Merve"); } * Örnek 9, #include #include #include #include #include #include template concept additive = requires (T x, T y){ x+y; // "T" türünden iki nesne "+" operatörünün operandı olduğunda, geçerli bir ifade olacak. x-y; // "T" türünden iki nesne "-" operatörünün operandı olduğunda, geçerli bir ifade olacak. }; template void func(T x) { if constexpr (additive){ std::cout << "necati ergin...\n"; } else { std::cout << "eray goksu...\n"; } } int main() { /* # OUTPUT # necati ergin... eray goksu... */ func(12); int x = 10; int* p = &x; func(p); } * Örnek 10, #include #include // Sadece tek bir "bit" i "1" olacak ve // "N" değeri de "32" den büyük olacak. template requires (std::has_single_bit(N) && N > 32) class Myclass{ }; int main() { /* # OUTPUT # */ // ERROR: template constraint failure for // ‘template requires std::has_single_bit(N) && N > 32 class Myclass’ Myclass<5> m1; // ERROR: template constraint failure for // ‘template requires std::has_single_bit(N) && N > 32 class Myclass’ Myclass<45> m2; Myclass<64> m3; // OK } * Örnek 11, #include constexpr bool is_prime(int x) { if (x < 2) return false; if (x % 2 == 0) return x == 2; if (x % 3 == 0) return x == 3; if (x % 5 == 0) return x == 5; for(int i{7}; i * i <= x; i += 2) if (x % i == 0) return false; return true; } template requires (is_prime(N)) class Myclass{}; template concept IsPrime = is_prime(N); template requires IsPrime void func() {} int main() { /* # OUTPUT # */ Myclass<17> m1; // OK Myclass<18> m1; // ERROR: template constraint failure for ‘template requires is_prime()(N) class Myclass’ func<19>(); // OK } >> "requires expression" : Diğerleri gibi derleme zamanında "boolean" değer üretmesi gerekmektedir. Kendi içerisinde üç aşama barındırır. Bunlar, "Simple Requiretments", "Type Requiretments", "Compound Requiretments", "Nested Requirements" aşamalarıdır. >>> "Simple Requiretments" : Belirtilen ifade, derleyici tarafından geçerli bir ifade olmalıdır. Örneğin, "T" herhangi bir tür olmak şartıyla, bu türden "x" değişkenimiz olsun. "x++;" ifadesi geçerli bir ifade olmalıdır. Bir diğer deyişle "x" değişkeni "++ son ek" operatörünün operandı olmalıdır. Burada "x" değişkeninin değeri bir arttırılmamaktadır. * Örnek 1, #include #include template concept Nec = requires (T x) { // Simple Requiretments x++; }; struct X{}; struct Y{ Y operator++(int){ return this; } }; int main() { /* # OUTPUT # */ static_assert(Nec); // "int" türden nesne "++" operatörünün operandı olabilir. static_assert(Nec); // "int*" türden nesne "++" operatörünün operandı olabilir. // "std::vector::iterator" nesnesi "++" operatörünün operandı olabilir. static_assert(Nec::iterator>); // "X" türünden nesne "++" operatörünün operandı olamayacağı için, sentaks hatası olacaktır. static_assert(Nec); static_assert(Nec); // "Y" türünden nesne "++" operatörünün operandı olabilir. } * Örnek 2, #include #include template concept Nec = requires (T x) { // Simple Requiretments *x; // "dereference" operatörünün operandı olabilecek. x[0]; // "[]" operatörünün operandı olabilecek. }; int main() { /* # OUTPUT # */ static_assert(Nec); // "int" türünden nesne içerik operatörünün operandı olamaz. static_assert(Nec); // "int*" türünden nesne hem içerik hem de "[]" operatörünün operandı olabilir. } * Örnek 3, #include #include #include #include template concept Neco = requires(T p) { p == nullptr; // "p" değişkeninin "nullptr" ile karşılaştırılabilir olması gerekmektedir. }; int main() { /* # OUTPUT # a: false b: true c: true */ std::boolalpha(std::cout); constexpr auto a = Neco; std::cout << "a: " << a << '\n'; constexpr auto b = Neco; std::cout << "b: " << b << '\n'; constexpr auto c = Neco>; std::cout << "c: " << c << '\n'; } * Örnek 4, #include #include #include #include template concept Neco = requires(T x /*, T y */) { x < x; // "x" değişkeninin "<" operatörünün operandı olabilmelidir. // x < y; // "T" türünden değişkenlerin, "<" operatörünün operandı olabilmeleri gerekmektedir. }; struct X{}; struct Y{ bool operator<(Y)const { return true; } }; int main() { std::boolalpha(std::cout); std::cout << Neco << ' ' << Neco << ' ' << Neco << '\n'; // true false true } * Örnek 5, #include #include #include #include #include template concept Neco = requires(T) { std::is_integral_v; // "std::is_integral_v" biçiminde yazmak SENTAKS HATASI OLMAMALI. // Yoksa buradaki "std::is_integral_v" ifadesinin değerinin ne olduğu // önemli değildir. }; int main() { constexpr auto a = Nec; // "a" is "true" } * Örnek 6, #include #include #include #include #include template concept Neco = requires(T p) { *p; **p; }; int main() { constexpr auto a = Nec; // "a" is "false" constexpr auto b = Nec; // "b" is "true" } * Örnek 7, #include #include #include #include #include template concept Neco = requires(T p) { ++**p; }; int main() { constexpr auto a = Nec; // "a" is false constexpr auto b = Nec; // "b" is "true" } * Örnek 8, #include #include #include #include #include template concept Printable = requires(T p) { std::cout << p; // "std::cout << p;" ifadesini yazmak SENTAKS HATASI OLMAMALIDIR. p.print(); // "p.print();" ifadesini yazmak SENTAKS HATASI OLMAMALIDIR. Bir diğer // deyişle "print" isminde üye fonksiyonunun olması gerekmektedir. }; struct A{}; struct B{ friend std::ostream& operator<<(std::ostream& os, const B& other) { return os; } }; struct C{ friend std::ostream& operator<<(std::ostream& os, const C& other) { return os; } void print() { } }; int main() { /* # OUTPUT # false false false false true */ constexpr auto a = Printable; // "a" is "false" constexpr auto b = Printable; // "b" is "false" constexpr auto c = Printable; // "c" is "false" constexpr auto d = Printable; // "d" is "false" constexpr auto e = Printable; // "e" is "true" std::boolalpha(std::cout); std::cout << a << '\n' << b << '\n' << c << '\n' << d << '\n' << e << '\n'; } * Örnek 9, #include #include #include #include #include template requires // start of "requires clause" requires(T x){ // start of "requires expression" x+x; // "T" türünden nesnelerin toplanabilir x-x; // ve çıkartılabilir olması gerekmektedir. }// end of "requires expression" && std::integral // "concepts" : "T" türünün bir tam sayı olması gerekmektedir. // end of "requires clause" class Myclass{}; int main() { Myclass m1; // OK Myclass m2; // ERROR: template constraint failure for // ‘template requires requires(T x) {x + x;x - x;} && (integral) class Myclass’ } * Örnek 10, #include #include #include #include #include template requires requires(T x){ std::cout << x; // "std::cout << x;" ifadesinin SENTAKS HATASI OLUŞTURMAMASI GEREKİYOR. *x; // "*x;" ifadesinin SENTAKS HATASI OLUŞTURMAMASI GEREKİYOR. sizeof(x) > 100; // "sizeof(x) > 100;" ifadesini yazmak, hiçbir zaman için, SENTAKS HATASI oluşturmaz. } class Myclass{}; template requires requires(T x){ std::cout << x; // "std::cout << x;" ifadesinin SENTAKS HATASI OLUŞTURMAMASI GEREKİYOR. *x; // "*x;" ifadesinin SENTAKS HATASI OLUŞTURMAMASI GEREKİYOR. } && (sizeof(T) > 4) // Artık "T" türünün "sizeof" değerinin de dörtten büyük olması gerekmektedir. class Neco{}; template concept my_custom_constraint = requires { // "sizeof(int) > 100;" ifadesi hiç bir zaman sentaks hatası oluşturmaz. Yani "Always True". sizeof(int) > 100; // "std::is_integral_v;" ifadesi de hiç bir zaman sentaks hatası oluşturmaz. Yani "Always True". std::is_integral_v; }; // Yani aslında hiç bir "constraints" mevcut değildir. int main() { constexpr bool a = my_custom_constraint; std::cout << "a: " << std::boolalpha << a << '\n'; // ERROR: template constraint failure for // ‘template requires requires(T x) {std::cout << x;*x;} && sizeof (T) > 4 class Neco’ constexpr bool b = Neco; // ERROR: template constraint failure for // ‘template requires requires(T x) {std::cout << x;*x;sizeof x > 100;} class Myclass’ constexpr bool c = Myclass; } * Örnek 11, #include #include #include #include #include template concept my_custom_constraint = requires { // "sizeof(int) > 100" ifadesinin "true" değer döndürmesi gerekmektedir. requires (sizeof(T) > 100); // "!std::is_integral_v" ifadesinin "true" değer döndürmesi gerekmektedir. requires (!std::is_integral_v); }; struct Buffer{ char m_buffer[2000]{}; }; int main() { std::cout << std::boolalpha << my_custom_constraint << '\n'; // true } * Örnek 12, #include #include template requires (sizeof(T) > 64) class Myclass {}; int main() { // Myclass m1; // ERROR: the associated constraints are not satisfied std::cout << "sizeof mt19937 : " << sizeof(std::mt19937) << '\n'; // sizeof mt19937 : 5000 Myclass eng; // OK } * Örnek 13, #include #include #include template requires std::is_pointer_v || std::same_as void foo(T x) {} template requires std::is_pointer_v || std::is_same_v void func(T x) {} int main() { int ival{}; foo(&ival); foo(nullptr); double dval{}; func(&dval); func(nullptr); } * Örnek 14, #include #include #include template requires (!std::convertible_to) void foo(T x) {} template requires std::convertible_to void func(T, U) {} template requires std::is_convertible_v void bar(T, U) {} int main() { foo("Merve"); // ERROR: the associated constraints are not satisfied foo(4.5); // OK func(2, 5.6); // OK int x{}; func(x, &x); // ERROR: the associated constraints are not satisfied } * Örnek 15, #include #include #include template requires std::integral())>> void foo(T) {} /* * (I) Buradaki "std::integral())>>" ifadesi, * "true" olması sentaks hatası oluşmayacaktır. * (II) Bunun için de "std::remove_reference_t())>" ifadesinin "std::integral" * "concept" ini karşılaması gerekmektedir. * (III) "std::declval()" ifadesi ise "T" türünden bir nesne oluşturma ifadesidir. Tıpkı "T{}" veya * "T()" gibi. Burada "declval" fonksiyon şablonunun kullanılma amacı, "T{}" veya "T()" biçimindeki * kullanımda ilgili tür için "Default Ctor." olması gerekmesidir. * (IV) "*std::declval()" ifadesi ise "std::declval()" ifadesinin "dereference" edilebilir olması * demektir. * (V) "decltype(*std::declval())" ifadesi ise "std::declval()" ifadesi "dereference" edildiğinde * oluşan ifadenin türü demektir. * (VI) "std::remove_reference_t())>" ifadesi ise yukarıda "decltype" ile elde * ettiğimiz türün referans olması halinde, referanslığını atmaktadır. * (VII) Böylelikle "std::integral())>>" ifadesiyle biz, * "T" türünden bir nesnenin "dereference" edildiğinde "std::integral" konseptini karşılayıp karşılamadığını * sorgulamış oluyoruz. */ int main() { int x{}; foo(&x); // "T" türü "int*" oldu. "dereference" edildiğinde "int" türü elde edildi. // "std::integral" konseptini karşılamış oldu. double dval{}; foo(&dval); // "T" türü "double*" olacaktır. "dereference" edildiğinde "double" türü elde // edilecektir. "std::integral" konsepti KARŞILANMAMIŞ olacaktır. std::optional op{ 45 }; foo(op); // "op" nesnesi "dereference" edildiğinde "int&" türü elde edilmekte. Referanslık // sıyrılacak ve "int" türü elde edilecek. Bu da "std::integral" konseptini karşılamış // oldu. } * Örnek 16.0, template concept my_custom_concept = requires(T x, U y) { x.foo() || y.bar(); // "x.foo() || y.bar();" ifadesinin yazılması sentaks hatası // OLUŞTURMAMALIDIR. }; struct A { void foo() {} }; struct B {}; struct C { void bar() {} }; int main() { constexpr auto a = my_custom_concept; // "a" is "false". Çünkü "B" sınıfında ".bar()" // fonksiyonu olmadığı için normalde sentaks // hatası oluşacaktır. Fakat burada konsept // karşılanmadığı için "false" değerini almıştır. // Dolayısıyla "||" operatörünü kullanırken dikkatli // olmalıyız. Normal şartlarda "||" operatörünün sol // operandı "true" ise sağ operandına bakılmaz. constexpr auto b = my_custom_concept; // Artık "b" değişkeninin değeri "true" olacaktır. Çünkü // her iki sınıf da bünyesinde ilgili fonksiyonları // barındırmaktadır. } * Örnek 16.1, #include #include template concept my_custom_concept = requires(T x) { x.foo(); } || requires (T x) { x.bar(); }; struct A { void foo() {} }; struct B {}; int main() { constexpr auto a = my_custom_concept; // "a" is "true". Çünkü artık // "requires(T x) { x.foo(); } || requires (T x) { x.bar(); }" // ifadesinin "true" olması halinde sonuç "true" olacaktır. Yani // "constraint" ifadelerinin kendilerinin "||" ile birleştirilmesi // gerekmektedir. } * Örnek 16.2, #include #include template concept has_foo = requires(T x) { x.foo(); }; template concept has_bar = requires(T x) { x.bar(); }; template requires has_foo || has_bar class Myclass {}; struct A { void foo() {} }; struct B {}; struct C { void bar() {} }; int main() { Myclass m1; Myclass m2; Myclass m3; } * Örnek 17, #include #include template concept my_concept_v1 = requires{ std::declval().foo(); }; template concept my_concept_v2 = requires (T x){ x.foo(); }; int main() { //... } >>> "Type Requiretments": Derleyici bir türün var olduğunu ve o türün kullanımının geçerli olduğunu sınamak zorunda. Burada "typename" anahtar sözcüğünü kullanmak zorundayız. Örneğin, "T" herhangi bir tür olmak şartıyla, "typename T::value_type;" ifadesi demek "T" türünün alt türü olarak "value_type" türünün olması gerekmektedir. Burada "using" bildirimi de kullanılabilir, "sub-class" olarak da. Önemli olan "value_type" a karşılık bir türün olmasıdır. * Örnek 1, #include #include #include #include #include template concept my_custom_constraint = requires { typename T::value_type; }; struct Eray{}; struct Neco{ using value_type = int; }; int main() { // "int" türü alt tür olarak "value_type" türüne // sahip olmadığından, "a" değişkeni "false" değerdedir. constexpr auto a = my_custom_constraint; // "std::vector" türü alt tür olarak // "value_type" türüne sahip olduğundan dolayı // "b" değişkeni "true" değerdedir. constexpr auto b = my_custom_constraint>; // "std::vector" türü alt tür olarak // "value_type" türüne sahip olduğundan dolayı // "c" değişkeni "true" değerdedir. constexpr auto c = my_custom_constraint::iterator>; // "Eray" türü "value_type" türünden alt türe sahip değildir. Bu yüzden // "d" değişkeni "false" değerdedir. constexpr auto d = my_custom_constraint; constexpr auto e = my_custom_constraint; // "Neco" türü "value_type" türünden alt türe sahiptir. Dolayısıyla // "e" değişkeni "true" değerdedir. } * Örnek 2, #include #include #include #include #include template class Myclass{ // Burada "T" türü herhangi bir tür olabilir. }; template class Neco{ // Burada "T" türü tam sayı türlerinden birisi olmak zorundadır. }; template concept Eray = requires{ typename Myclass; // "T" türü ne ise "Myclass" sınıf şablonunun // o tür nezdinde açılabilir olması gerekiyor. }; template concept Deniz = requires{ typename Neco; // "T" türü ne ise "Neco" sınıf şablonunun // o tür nezdinde açılabilir olması gerekiyor. }; int main() { constexpr auto a = Eray; // "T" için "int" gelmekte. "Myclass" geçerli // olması hasebiyle, "a" değişkeni "true" değerde. constexpr auto b = Deniz; // "T" için "double" gelmekte. "Neco" geçerli // değildir çünkü "Neco" için "T" ye karşılık sadece // tam sayı türleri gelebilir. Dolayısıyla "b" değişkeni // "false" değerdedir. } * Örnek 3, #include #include #include #include #include // Way - I template concept check_if_moveable_v1 = requires { requires (std::is_move_constructible_v); }; // Way - II template concept check_if_moveable_v2 = (std::is_move_constructible_v); int main() { //... } >>> "Compound Requirements" : "{}" kullandığımız durumlardır. Üç farklı yöntemi vardır. >>>> Sadece "{}" kullanılması: * Örnek 1, #include #include template concept Nec = requires (T x){ // Compound Requirement {x.foo()}; // x.foo(); }; struct A{}; struct B{ void foo(void) {} }; struct C{ void foo(void) noexcept {} }; int main() { std::boolalpha(std::cout); std::cout << Nec << '\n'; // false std::cout << Nec << '\n'; // true std::cout << Nec << '\n'; // true } >>>> "{}" ile birlikte "noexcept" gibi şeylerin kullanılması: * Örnek 1, #include #include template concept Nec = requires (T x){ // Compound Requirement {x.foo()}; // x.foo(); {*x}noexcept; // "*x" ifadesinin yürütülmesi "noexcept" garantisi vermektedir. }; struct A{}; struct B{ void foo(void) {} }; struct C{ void foo(void) noexcept {} }; struct D{ void foo(void) {} D& operator*() { return *this; } }; struct G{ void foo(void) {} G& operator*() noexcept { return *this; } }; struct E{ void foo(void) noexcept {} E& operator*() { return *this; } }; struct F{ void foo(void) noexcept {} F& operator*() noexcept { return *this; } }; int main() { std::boolalpha(std::cout); std::cout << Nec << '\n'; // false std::cout << Nec << '\n'; // false std::cout << Nec << '\n'; // false std::cout << Nec << '\n'; // false std::cout << Nec << '\n'; // false std::cout << Nec << '\n'; // true std::cout << Nec << '\n'; // true } >>>> "{}" ile birlikte "->" operatörünün kullanılması. Bu kullanım ile amaç "{}" içerisindeki ifadeden elde edilmesi gereken türün ne olması gerektiği belirlenmektedir. Fakat türü doğrudan yazamıyoruz. Buradaki türü bir "concept" olarak belirmemiz gerekmektedir. Dolayısıyla "std::same_as" konseptini kullanmalıyız. Buradaki çalışma mekanizması da aşağıdaki gibidir: //... {x.foo()} -> std::same_as; //... Normal şartlarda "std::same_as" konsepti iki parametrelidir. Buradaki parametrelerden birisi "decltype(x.foo())" ile otomatik olarak elde edilirken, ikinci parametre ise bizim belirttiğimiz "int" türüdür. Benzer şekilde "{}" içerisindeki ifadenin türünün tam sayı olmasını istiyorsak; //... {x.foo()} -> std::integral; //... Buradaki "std::integral" in tek parametresi ise "decltype(x.foo())" ile elde edilmiş olduğundan, nihai hedefe ulaşmış oluyoruz. * Örnek 1, #include #include template concept Nec = requires (T x){ // Compound Requirement {x.foo()} noexcept -> std::integral; }; struct A{}; struct B{ void foo(void) {} }; struct C{ void foo(void) noexcept {} }; struct D{ int foo(void) noexcept { return 1; } }; int main() { std::boolalpha(std::cout); std::cout << Nec << '\n'; // false std::cout << Nec << '\n'; // false std::cout << Nec << '\n'; // false std::cout << Nec << '\n'; // true } * Örnek 2, #include #include template concept any_concept = requires (T x, U y){ { x+y } -> std::common_with; // "x+y" ifadesinin türü "std::common_type" // ifadesinin türü olmalıdır. }; struct A{}; struct B{ B() = default; B(int) {} }; struct C{ C() = default; C(int) {} operator int() { return 1; } }; int main() { std::boolalpha(std::cout); std::cout << any_concept << '\n'; // true std::cout << any_concept << '\n'; // true std::cout << any_concept << '\n'; // true std::cout << any_concept << '\n'; // true std::cout << any_concept << '\n'; // true std::cout << any_concept << '\n'; // false std::cout << any_concept << '\n'; // false std::cout << any_concept << '\n'; // true } >>> "Nested Requirements" : "Require Expression" içerisinde "Require Closure" kullanılmasıdır. * Örnek 1, #include #include /* (IV) template concept Reference = std::is_reference_v; */ template concept Nec = /* (I) => */ /* std::is_reference_v && */ requires (T x){ std::is_reference_v; // "std::is_reference_v;" ifadesini yazmak sentaks hatası // OLUŞTURMAMALI. Burada "T" türünün referanslığı sorgulanmamaktadır. // Eğer "T" türünün referanslığının sorgulanmasını istiyorsak // ya "I" ya "II" ya da "III" nolu yöntemi kullanabiliriz. // (II) // requires std::is_reference_v; // (III) // requires requires (T) { std::is_reference_v; } // Hatta referans sorgulamasını ayrı bir konsept altında da yapabiliriz. (IV) // requires Reference; }; int main() { //... } Şimdi de kritik noktaları gösteren örnekleri inceleyelim: * Örnek 1, #include #include template concept c1 = std::integral; template concept c2 = requires { std::integral; // !!! }; template concept c3 = requires { requires std::integral; }; template class Myclass{}; template concept c4 = requires { typename Myclass; }; int main() { /* # OUTPUT # true false true true true false true false */ std::boolalpha(std::cout); std::cout << c1 << ' ' << c1 << '\n'; std::cout << c2 << ' ' << c2 << '\n'; std::cout << c3 << ' ' << c3 << '\n'; std::cout << c4 << ' ' << c4 << '\n'; } * Örnek 2, #include #include // constraint: // *p && p[] // WAY - I template concept Nec_v1_1 = requires (T p){ *p; p[0]; }; // WAY - II template concept Dereferencable_v1 = requires (T x) { *x; }; template concept Subscriptable_v1 = requires (T x) { x[0]; }; template concept Nec_v1_2 = Dereferencable_v1 && Subscriptable_v1; // constraint: // *p || p[] // WAY - I template concept Dereferencable_v2 = requires (T x) { *x; }; template concept Subscriptable_v2 = requires (T x) { x[0]; }; template concept Nec_v2_1 = Dereferencable_v2 || Subscriptable_v2; // WAY - II template concept Nec_v2_2 = requires (T x) { *x; } || requires (T x) { x[0]; }; // WAY - III: Different Meaning!!! template concept Nec_v2_3 = requires (T x){ *x || x[0]; }; int main() { /* # OUTPUT # */ //... } * Örnek 3, #include #include template concept hashable = requires { // typename std::hash; // "typename std::hash" ifadesi "std::hash" türü oluşturulabilir mi demektir. // "std::hash" in herhangi bir türden açılımı oluşturulabilir. std::hash{}; // "std::hash" in "T" açılımı türünden bir nesneyi oluşturabiliriz, demektir. }; /* // WAY - I template requires hashable void foo(T) { std::hash x; //... } */ /* // WAY - II template void bar(T) { std::hash x; //... } */ // WAY - III void func(hashable auto) { //... } struct A{}; template<> struct std::hash{ std::size_t operator()(const A&)const { return 3; } }; int main() { func(A{}); // OK: Çünkü yukarıda "std::hash" için kendi sınıfımız için // açılabilir olmasını kıldık. Eğer "template<>" kısmı // olmasaydı, bu kod parçası sentaks hatası oluşturacaktı. // Böylelikle bizler "custom" türlerimiz için "std::hash" açılımının // olup olmadığını sorgulayabiliriz. } * Örnek 4, #include #include template concept HasFoo = requires (T x) { // "x.foo()" ile elde ettiğimiz tür, bool" türüne dönüştürülebilir olmalıdır. { x.foo() } noexcept -> std::convertible_to; // "x.bar()" ile elde ettiğimiz tür ise "bool" türü olmalıdır. { x.bar() } noexcept -> std::same_as; }; struct Nec{ int foo()const noexcept { return 1; } int bar()const noexcept { return 11; } }; struct Erg{ int foo()const noexcept { return 1; } bool bar()const noexcept { return 11; } }; int main() { std::cout << HasFoo << '\n'; // false std::cout << HasFoo << '\n'; // true } * Örnek 5, #include #include // LEVEL - I template concept HasTheFunctions = requires (T x) { // "x.foo()" ile elde ettiğimiz tür, bool" türüne dönüştürülebilir olmalıdır. { x.foo() } noexcept -> std::convertible_to; // "x.bar()" ile elde ettiğimiz tür ise "bool" türü olmalıdır. { x.bar() } noexcept -> std::same_as; }; template requires HasTheFunctions class StepOne{}; template requires HasTheFunctions T FuncOne(T x) { return x; } template T FooOne(T x) requires HasTheFunctions { return x; } // LEVEL - II template concept IsItFinal = std::integral && HasTheFunctions; template requires IsItFinal class StepTwo{}; template requires IsItFinal T FuncTwo(T x) { return x; } template T FooTwo(T x) requires IsItFinal { return x; } // LEVEL - III template // "Constrained Template Param." class Last{}; template T FuncThree(T x) { return x; } // LEVEL - IV auto FuncFour(IsItFinal auto x) { return x; } // "Aggregate Template Syntax" IsItFinal auto FooFour(IsItFinal auto x) { return x; } // "Aggregate Template Syntax" int main() { //... } * Örnek 6, #include #include int main() { auto f = [](T x) {}; f(12); auto g = [](T x){}; g(12); auto h = [](T x)-> typename T::value_type {}; h(12); auto i = [](T x)-> double{ return 1.; }; i(12); auto j = [](std::integral auto)->std::integral auto { return 1; }; j(12); auto k = [](T x)-> T {}; k(12); } * Örnek 7, #include #include template concept nec = requires (T x) { {++x}noexcept->std::convertible_to; requires std::integral; }; int main() { //... } * Örnek 8, #include #include template concept Necible = requires (T t, U u, W w){ ++t; --u; w.foo(); }; // Buradaki fonksiyon parametresi olarak diğeri derleyici tarafından yazıldığı için yazılmamıştır. void foo(Necible auto) {} int main() { } Bir fonksiyonun sadece belirli bir argümanla çağrılmasına izin vermek; örneğin, sadece "int" ile çağrılabilen bir fonksiyon: * Örnek 1.0, Klasik yöntem. #include #include template void func(T) = delete; void func(int) {} int main() { /* * Yukarıdaki iki "func" fonksiyonu da birbirini * "overload" etmektedir. Dilin kuralına göreyse; * Argümanla parametre uyuyorsa, gerçek fonk. * Argümanla parametre uymuyorsa, o şablondan bir * "specialization" oluşturulacak. Yukarıda da bu * "specialization" "delete" edildiği için sentaks * hatası oluşacak. */ func(12); // OK func(1.2); // ERROR: use of deleted function ‘void func(T) [with T = double]’ func(1.2f); // ERROR: use of deleted function ‘void func(T) [with T = float]’ func('f'); // ERROR: use of deleted function ‘void func(T) [with T = char]’ func(12u); // ERROR: use of deleted function ‘void func(T) [with T = unsigned int]’ } * Örnek 1.1, "Substitution Failure is not an error" / "SFINAE": #include template> * = nullptr> void func(T); int main() { func(12); // OK func(1.2); // ERROR: no matching function for call to ‘func(double)’ func(1.2f); // ERROR: no matching function for call to ‘func(float)’ func('f'); // ERROR: no matching function for call to ‘func(char)’ func(12u); // ERROR: no matching function for call to ‘func(unsigned int)’ } * Örnek 1.2.0, #include #include template requires std::same_as void func(T x) {} int main() { func(12); // OK func(1.2); // ERROR: no matching function for call to ‘func(double)’ func(1.2f); // ERROR: no matching function for call to ‘func(float)’ func('f'); // ERROR: no matching function for call to ‘func(char)’ func(12u); // ERROR: no matching function for call to ‘func(unsigned int)’ } * Örnek 1.2.1, #include #include template T> void func(T x) {} int main() { func(12); // OK func(1.2); // ERROR: no matching function for call to ‘func(double)’ func(1.2f); // ERROR: no matching function for call to ‘func(float)’ func('f'); // ERROR: no matching function for call to ‘func(char)’ func(12u); // ERROR: no matching function for call to ‘func(unsigned int)’ } * Örnek 1.2.2, #include #include void func(std::same_as auto x) {} int main() { func(12); // OK func(1.2); // ERROR: no matching function for call to ‘func(double)’ func(1.2f); // ERROR: no matching function for call to ‘func(float)’ func('f'); // ERROR: no matching function for call to ‘func(char)’ func(12u); // ERROR: no matching function for call to ‘func(unsigned int)’ } Standart konseptleri inceleyelim: * Örnek 1, #include #include // WAY - I template void func1(F fn) { fn(100); } // WAY - II template requires std::invocable void func2(F fn) { fn(100); } // WAY - III template void func3(F fn) requires std::invocable { fn(100); } // WAY - IV template F> void func4(F fn) { fn(100); } // WAY - V void func5(std::invocable auto f) { f(100); } void func5(std::invocable auto f) { f(100, 100.001); } auto f = [](int x){ std::cout << x*x << '\n'; }; auto g = [](int x, double y){ std::cout << x+x * y+y<< '\n'; }; int main() { func1(f); func2(f); func3(f); func4(f); func5(f); func5(g); } * Örnek 2, #include #include #include void func(std::regular auto x) {} // "regular" olabilmesi için "Default Init.", // "Copyable", "Moveable", "Swapable" ve // "Equality Comparison" işlevlerini desteklemeli. void func(std::semiregular auto x) {} // "semiregular" supports "Default Init.", "Copy", // "Move" and "Swapable". struct Merve{}; struct Menekse{ bool operator==(const Menekse&) const = default; }; int main() { func(12); // OK: Regular func(std::string{"Merve"}); // OK: Regular func(Merve{}); // OK: Semiregular func(Menekse{}); // OK: Regular } "concepts" kullanmanın ilave bir avantajı ise "overloading" tarafıdır. Fakat buna yönelik de bir takım kurallar vardır. * Örnek 1, #include #include #include void foo(std::integral auto) { std::cout << "integral\n"; } void foo(std::unsigned_integral auto) { std::cout << "unsigned_integral\n"; } int main() { foo(12); // integral foo(12u); // unsigned_integral foo(21L); // integral foo(21uL); // unsigned_integral } * Örnek 2, #include #include #include template concept Nec = requires (T x) { x.foo(); }; template concept Erg = Nec && requires (T x) { x.bar(); }; void func(Nec auto) { std::cout << "Nec auto\n"; } void func(Erg auto) { std::cout << "Erg auto\n"; } struct A{ void foo() {} }; struct B{ void foo() {} void bar() {} }; int main() { func(A{}); // Nec auto func(B{}); // Erg auto } * Örnek 3, #include #include #include template concept Nec = requires (T x) { x.foo(); }; template concept Erg = Nec || requires (T x) { x.bar(); }; void func(Nec auto) { std::cout << "Nec auto\n"; } void func(Erg auto) { std::cout << "Erg auto\n"; } struct A{ void foo() {} }; struct B{ void foo() {} void bar() {} }; int main() { func(A{}); // Nec auto func(B{}); // Nec auto } Şimdi de "concepts" ler arasındaki kısıtlama ilişkisine değinelim: * Örnek 0, #include #include template concept integral_or_floating = std::integral || std::floating_point; template concept integral_and_char = std::integral && std::same_as; void f(std::integral auto) { std::cout << "std::integral\n"; } // 1 void f(integral_or_floating auto) { std::cout << "std::integral_or_floating\n"; } // 2 void f(std::same_as auto) { std::cout << "same_as\n"; } // 3 // void f(integral_and_char auto) { std::cout << "integral_and_char\n"; } // 4 int main() { f(5); // Burada daha kısıtlayıcı hangisi ise o seçilecektir. // Yani ya "1" ya "2" seçilecektir. "2" için "||" operatörü // kullanıldığı için, "1" den daha kısıtlayıcı DEĞİLDİR. Bu // durumda "1" daha kısıtlayıcıdır. f(2.3); // Sadece "2" uygundur. O çağrılacaktır. f('A'); // "1", "2" ve "3" uymaktadır. Fakat buradaki konseptler arasında bir // kısıtlayıcı etki YOKTUR. Dolayısıyla şu anda "ambiguous" oluşacaktır. // Bu hatayı gidermek için "4" numara gibi bir fonksiyon yazmalıyız. } * Örnek 1.0, #include template concept has_foo = requires (T t) { t.foo(); }; template concept has_foo_bar = requires (T t) { t.foo(); t.bar(); }; void func(has_foo auto) {} void func(has_foo_bar auto) {} struct Nec { void foo() {} }; struct Erg{ void foo() {} void bar() {} }; int main() { func(Erg{}); // Görünürde daha kısıtlayıcı olan "has_foo_bar" olandır. // Fakat bu iki konsept arasında "subsumption" olmadığından // "ambigous" hatası oluşacaktır. Alttaki konseptin üsttekini // "&&" operatörü ile birlikte kapsaması gerekmektedir. } * Örnek 1.1, #include template concept has_foo = requires (T t) { t.foo(); }; template concept has_foo_bar = has_foo && requires (T t) { t.bar(); }; void func(has_foo auto) {} void func(has_foo_bar auto) {} struct Nec { void foo() {} }; struct Erg{ void foo() {} void bar() {} }; int main() { func(Erg{}); // Alttaki olan üsttekini "&&" operatörü ile "subsume" etmiştir. // Artık "has_foo_bar" parametreli fonksiyon çağrılacaktır. } * Örnek 2.0, #include template concept Integral = requires (T x) { requires std::is_integral_v; }; template concept SignedIntegral = requires (T x) { requires std::is_integral_v; requires std::is_signed_v; }; void f(Integral auto) {} void f(SignedIntegral auto) {} int main() { f(12); // Yine iki konsept arasında kapsayıcılık ilişkisi // olmadığından, "ambigous" hatası oluşacaktır. } * Örnek 2.1, #include template concept Integral = requires (T x) { requires std::is_integral_v; }; template concept SignedIntegral = Integral && requires (T x) { requires std::is_signed_v; }; void f(Integral auto) {} void f(SignedIntegral auto) {} int main() { f(12); // Alttaki, üstekini "subsume" edeceğinden ilgili // sentaks hatası ortadan kalkacaktır. } * Örnek 2.2, #include template concept Integral = requires (T x) { requires std::is_integral_v; }; template concept SignedIntegral = Integral || requires (T x) { requires std::is_signed_v; }; void f(Integral auto) {} void f(SignedIntegral auto) {} int main() { f(12); // Bu durumda "subsumtion" ilişkisi farklı // olacaktır. Artık "Integral" parametreli // fonksiyon çağrılacaktır. Eğer "&&" opt. // kullanılsaydı, "SignedIntegral" parametreli // fonksiyon çağrılacaktır. } * Örnek 3, #include #include void foo_v1(const std::convertible_to auto& x) { std::string str = x; //... } void foo_v2(const auto& x) requires std::convertible_to { std::string str = x; //... } template T> void foo_v3(const T& x) { std::string str = x; //... } template requires std::convertible_to void foo_v4(const T& x) { std::string str = x; //... } int main() { //... } * Örnek 4, #include #include #include #include template T> void foo(T x) { std::cout << static_cast(x) << '\n'; } int main() { std::boolalpha(std::cout); int x{ 43 }; foo(x); // "int" türünden "bool" türüne dönüşüm geçerlidir. foo(&x); // Gösterici türlerden "bool" türüne dönüşüm geçerlidir. foo(nullptr); // "nullptr" -> "bool" türüne örtülü dönüşüm mevcut değildir. foo("murat"); // Gösterici türlerden "bool" türüne dönüşüm geçerlidir. "T" is "const char*". foo(std::string{"murat"}); // "std::string" -> "bool" türüne örtülü dönüşüm mevcut değildir. foo(std::make_unique(43)); // İlgili tür dönüştürme operatör fonksiyonu "explicit" olduğu için // sentaks hatası meydana gelmektedir. } * Örnek 5, #include template class Myclass {}; struct A {}; struct B { B() = default; B(const B&) = default; B& operator=(const B&) = default; }; struct C { C() = delete; C(const C&) = delete; C& operator=(const C&) = delete; }; struct D { D() = default; D(const D&) = default; D& operator=(const D&) = default; D(D&&) = delete; D& operator=(D&&) = delete; }; int main() { Myclass m1; Myclass m2; Myclass m3; Myclass m4; // "C" is not copyable. Myclass m5; // "D" is not copyable. } * Örnek 6, #include #include #include #include template void bar(F fn, Iter iter) requires std::indirect_unary_predicate { std::cout << fn(*iter) << '\n'; } int main() { const auto pred = [](int x) { return x % 5 == 0; }; std::vector ivec{ 4, 50, 12, 20, 35, 7, 9, 40 }; std::boolalpha(std::cout); for (auto iter = ivec.begin(); iter != ivec.end(); ++iter) { bar(pred, iter); } } * Örnek 7, #include #include #include #include void func(std::invocable auto f, int x) { f(x); } void func(std::invocable auto f, int x) { f(x); } int main() { f( []() {}, 20 ); // ERROR f( [](int x) { return x * x; }, 20 ); // OK f( [](int x, double y) { return x * y; }, 40, 40. ); // OK } * Örnek 8, #include #include void foo(std::totally_ordered auto x) { //... } struct Nec {}; struct Erg { auto operator<=>(const Erg&) const = default; }; struct Cpp { bool operator==(const Cpp&) const = default; }; int main() { foo(1); foo(std::string{ "Merve" }); foo(Nec{}); // ERROR foo(Erg{}); foo(Cpp{}); // ERROR } > Hatırlatıcı Notlar: >> "std::declval" : Derleme zamanında çağrılan bir fonksiyondur. Geri dönüş değeri, o türe ilişkin bir referanstır. "R-Value" veya "L-Value" referans olması fark etmemektedir. * Örnek 1, class Necati{ public: Myclass(); int foo() const { return 1; } }; class Ergin{ public: Myclass(int); int foo() const { return 2; } }; int main() { decltype(Necati{}.foo()); // Bu ifade sentaks hatası oluşturmayacaktır. decltype(Ergin{}.foo()); // Bu ifade sentaks hatası oluşturacaktır. Çünkü sınıfın "Default Ctor." YOKTUR. decltype(std::declval().foo()); // Artık sentaks hatası oluşmayacaktır. Fakat bu fonksiyonu sadece // "Unevaluated Context" içerisinde çağırmak zorundayız. } >> Uzun fonksiyonların nasıl daha kısa gale getirildiğine ilişkin örnek: * Örnek 1, #include #include #include // WAY - I void aggregateAndDisplay_v1( std::map const& source, std::map const& destination ) { auto aggregatedMap = destination; for(auto const& sourceEntry: source){ auto destinationPosition = aggregatedMap.find(sourceEntry.first); if(destinationPosition == aggregatedMap.end()) aggregatedMap.insert(std::make_pair(sourceEntry.first, sourceEntry.second)); else aggregatedMap[sourceEntry.first] = sourceEntry.second + " or " + destinationPosition->second; } for(auto const& entry: aggregatedMap){ std::cout << "Available translation for " << entry.first << " : " << entry.second << '\n'; } } // WAY - II void aggregateAndDisplay_v2( std::map const& source, std::map const& destination ) { const auto aggregatedMap = [&]() { auto aggregatedMap = destination; for(auto const& sourceEntry: source){ auto destinationPosition = aggregatedMap.find(sourceEntry.first); if(destinationPosition == aggregatedMap.end()) aggregatedMap.insert(std::make_pair(sourceEntry.first, sourceEntry.second)); else aggregatedMap[sourceEntry.first] = sourceEntry.second + " or " + destinationPosition->second; } return aggregatedMap; }(); for(auto const& entry: aggregatedMap){ std::cout << "Available translation for " << entry.first << " : " << entry.second << '\n'; } } // WAY - III void aggregateAndDisplay_v3( std::map const& source, std::map const& destination ){ const auto aggregatedMap = []( std::map const& destination, std::map const& source ) { auto aggregatedMap = destination; for(auto const& sourceEntry: source){ auto destinationPosition = aggregatedMap.find(sourceEntry.first); if(destinationPosition == aggregatedMap.end()) aggregatedMap.insert(std::make_pair(sourceEntry.first, sourceEntry.second)); else aggregatedMap[sourceEntry.first] = sourceEntry.second + " or " + destinationPosition->second; } return aggregatedMap; }(source, destination); for(auto const& entry: aggregatedMap){ std::cout << "Available translation for " << entry.first << " : " << entry.second << '\n'; } } // WAY - IV namespace{ auto aggregateAndDisplay( std::map const& destination, std::map const& source ){ auto aggregatedMap = destination; for(auto const& sourceEntry: source){ auto destinationPosition = aggregatedMap.find(sourceEntry.first); if(destinationPosition == aggregatedMap.end()) aggregatedMap.insert(std::make_pair(sourceEntry.first, sourceEntry.second)); else aggregatedMap[sourceEntry.first] = sourceEntry.second + " or " + destinationPosition->second; } return aggregatedMap; } }; void aggregateAndDisplay_v4( std::map const& source, std::map const& destination) { auto aggregatedMap = aggregateAndDisplay(destination, source); for(auto const& entry: aggregatedMap){ std::cout << "Available translation for " << entry.first << " : " << entry.second << '\n'; } } int main() { std::map m1{ { 10, "CPU" }, { 15, "GPU" }, { 20, "RAM" } }; std::map m2; /* # OUTPUT # Available translation for 10 : CPU Available translation for 15 : GPU Available translation for 20 : RAM */ aggregateAndDisplay_v4(m1, m2); /* # OUTPUT # Available translation for 10 : CPU Available translation for 15 : GPU Available translation for 20 : RAM */ aggregateAndDisplay_v3(m1, m2); /* # OUTPUT # Available translation for 10 : CPU Available translation for 15 : GPU Available translation for 20 : RAM */ aggregateAndDisplay_v2(m1, m2); /* # OUTPUT # Available translation for 10 : CPU Available translation for 15 : GPU Available translation for 20 : RAM */ aggregateAndDisplay_v1(m1, m2); }