> "constexpr" Fonksiyonlar: "Updates on constexpr functions from C++11 to C++20" isimli resim dosyasından da "constexpr" fonksiyonların yaşadığı değişimi görebiliriz. >> "constexpr" bir fonksiyonun derleme aşamasında çağrılmasını garanti altına almak için fonksiyona gönderilen ifade bir sabit ifadesi olmalı ve "constant expression" gereken yerde bu fonksiyonun çağrılması gerekmektedir. * Örnek 1, constexpr int foo(int x) { return x+5; } int main() { int x = foo(10); // Burada "foo" fonksiyonun derleme zamanında çağrılması // derleyicinin yaptığı bir optimizasyondur. } * Örnek 2, #include #include #include #include constexpr int foo(int x) { if(std::is_constant_evaluated()){ return x*x*x; // Sabit ifadesi gereken bir bağlamda kullanılırsa, buradaki kod çalıştırılacak. } else{ return x*x; // Aksi halde buradaki. } } int main() { int x = foo(10); // Burada "foo" fonksiyonun derleme zamanında çağrılması // derleyicinin yaptığı bir optimizasyondur. std::cout << "x : " << x << '\n'; // x : 100 const int y = foo(10); std::cout << "y : " << y << '\n'; // y : 1000 constexpr int z = foo(10); std::cout << "z : " << z << '\n'; // z : 1000 int a[foo(2)]; // int a[8]; std::array arr; // std::array arr; if constexpr (foo(5) > 40){ std::cout << "Ali\n"; // Bu kod alınacak. Ekrana "Ali" yazacaktır. } else{ std::cout << "Veli\n"; } if(foo(5) == 125){ std::cout << "Ali\n"; } else{ std::cout << "Veli\n"; // Bu kod alınacak. Ekrana "Veli" yazacaktır. } static_assert(foo(5) == 125); // "fail" OLMAYACAKTIR. std::bitset bs; // std::bitset<1000> bs; } >> "constexpr" bir fonksiyonun derleme aşamasnda çağrılmasını garanti ettiğimiz bir senaryoda tanımsız davranış varsa, derleyici sentaks hatası vermek zorundadır. * Örnek 1, int foo_ub(int idx) { int ar[] = { 2, 3, 5, 7, 9, 11, 13, 15, 17, 19 }; return ar[idx]; } constexpr int foo(int idx) { int ar[] = { 2, 3, 5, 7, 9, 11, 13, 15, 17, 19 }; return ar[idx]; } constexpr int factorial(int n) { return n < 2 ? 1 : n * factorial(n-1); } constexpr int shift(int x, int n) { return x << n; } int main() { int a = foo_ub(20); // Tanımsız Davranış oluşacaktır. Çünkü dizinin boyutu taşmıştır. // ERROR: call to non-‘constexpr’ function ‘int foo_ub(int)’ // "constexpr" bir nesneye ilk değer veren ifadenin "constant expression" olması gerekmektedir. constexpr int b = foo_ub(20); // ERROR: array subscript value ‘20’ is outside the bounds of array ‘ar’ of type ‘int [10]’ // "constexpr" bir fonksiyonu derleme aşamasında çağırmaya zorlarsak ki burada zorluyoruz, derleyici // tanımsız davranışı tespit edip, Tanımsız Davranış olması durumunda, sentaks hatası olarak değerlendirmek // zorundadır. constexpr int c = foo(20); // Tanımsız Davranış olacaktır. Çünkü 12! itibariyle "4-byte" işaretli tam sayılarda taşma oluşmaktadır. // İşaretli tam sayılarda taşma ise Tanımsız Davranıştır. int d = factorial(14); // ERROR: overflow in constant expression [-fpermissive] // Artık bu noktada sentaks hatası oluşacaktır, yukarıdaki taşmanın getirdiği tanımsız davranıştan ötürü. constexpr int e = factorial(14); // Sentaks hatası oluşacaktır. Çünkü tam sayının bit adedine eşit ya da ondan büyük değerde olan sağ operand, // sağa ya da sola kaydırma fark etmeksizin, tanımsız davranış oluşturur. Yukarıdaki sebepten ötürü de // sentaks hatası oluşmaktadır. "32-bit" değerindeki bir sayıyı en fazla "31" bit kaydırabiliriz. constexpr int f = shift(20, 32); } >> "constexpr" fonksiyonlarda "throw" ifadelerini kullanabiliyoruz fakat çalışma zamanına ilişkin bağlamda kullanmalıyız. * Örnek 1, #include #include constexpr int factorial(int n) { if (n < 0) throw std::runtime_error{"negative factorial argument!"}; int result{1}; for(int i = 1; i <= n; ++i) result *= i; return result; } int main() { int ival; std::cout << "Bir tam sayi girin: "; std::cin >> ival; try{ /* * Şimdi çalışma zamanında "ival" değişkeni negatif bir değer * alırsa, "Run Time Error" oluşacaktır. */ auto x = factorial(ival); } catch(const std::exception& ex){ std::cout << "Caught exception: " << ex.what() << '\n'; } constexpr auto x = factorial(ival); // Sentaks hatası oluşacaktır. /* * Böylelikle hem çalışma zamanında "throw" ettirme imkanı hem de * derleme zamanında sentaks hatası oluşturabilme imkanı elde etmiş * oluyoruz. */ } >> Algoritmaların tamamına yakını "constexpr". * Örnek 1, #include #include #include #include constexpr int foo(int n) { int ar[10] = { 1, 3, 5, 7, 9, 11, 13, 15 }; //... std::sort(std::begin(ar), std::end(ar), std::greater{}); return ar[n]; } constexpr int foo(int x, int y) { int ar[10] = { 1, 3, 5, 7, 9, 11, 13, 15 }; //... return std::accumulate(std::next(std::begin(ar), x), std::next(std::begin(ar), y), 0); } // Alternative Way 1 constexpr void foo(double* p) { delete []p; }; constexpr int factorial(int n) { return n < 2 ? 1 : n * factorial(n - 1); } constexpr double get_e(int n) { double* p = new double[n]; for(int i{}; i < n; ++i) p[i] = 1. / factorial(i); auto sum = std::accumulate(p, p + n, 0.); //delete p; // Bu şekilde bir "delete" işlemi tanımsız davranış olduğundan, sentaks hatası olmuştur. // ERROR: non-array deallocation of object allocated with array allocation // Eğer "deallocation" işlemini hiç yapmazsak, tanımsız davranış oluşacağından, sentaks hatası alırız. // ERROR: ‘get_e(10)’ is not a constant expression because allocated storage has not been deallocated delete []p; // OK //foo(p); // OK - Alternative Way 1 return sum; } int main() { constexpr auto val1 = foo(2); // "val1" değişkeninin değeri artık derleme zamanında hesaplanmıştır. constexpr auto val2 = foo(2, 5); // "val2" değişkeninin değeri artık derleme zamanında hesaplanmıştır. constexpr auto val3 = get_e(10); // "val3" değişkeninin değeri artık derleme zamanında hesaplanmıştır. } * Örnek 2, #include #include #include #include constexpr int foo(int n) { std::vector vec{ 1, 4, 7, 2, 3, 9, 6, 7, 5 }; std::sort(vec.begin(), vec.end()); return std::accumulate(vec.begin(), std::next(vec.begin(), n), 0); } int main() { constexpr int x = foo(3); // "x" will be evaluated in compile time. } * Örnek 3, #include #include #include #include constexpr int get_median(std::vector vec) { std::sort(vec.begin(), vec.end()); return vec[vec.size() / 2]; } int main() { constexpr auto median = get_median({ 2, 4, 6, 1, 9, 3, 12, 67, 982, 4, 6 }); } * Örnek 4, #include #include #include constexpr std::vector split(std::string_view strv, std::string_view delims = " ") { std::vector output; size_t first = 0; while(first < strv.size()){ const auto second = strv.find_first_of(delims, first); if(first != second) output.emplace_back(strv.substr(first, second - first)); if(second == std::string_view::npos) break; first = second + 1; } return output; } constexpr size_t numWords(std::string_view str) { const auto words = split(str); return words.size(); } int main() { static_assert(numWords("hello world abc xyz") == 4); // Test-case I constexpr auto val = numWords("ali veli hasan necati handan rukiye murat"); // Test-case II /* * Yine buradaki "val" değişkeninin değeri de derleme zamanında hesaplanmıştır. */ } * Örnek 5, #include struct Point{ constexpr Point& operator+=(const Point& other) noexcept { mx += other.mx; my += other.my; return *this; } double mx{}, my{}; }; constexpr bool test(int n) { std::vector vec(n); for(auto& pt : vec) pt = new Point{ 0., 1 }; Point sum{}; for(auto& pt : vec) sum += *pt; for(auto& pt : vec) delete pt; return static_cast(sum.my) == n; } int main() { static_assert(test(10)); } > "consteval" Fonksiyonlar: "constexpr" fonksiyonlardan farklı olarak, "compile time context" içerisinde çağrılmaları GARANTİ altındadır. Yani derleme zamanı bağlamında çağrılması mümkün değilse, SENTAKS HATASI oluyor. Yani sadece derleme zamanında çağrılabilir. Koşul kümesinin "constexpr" fonksiyonlarınınki ile aynı olduğunu düşünebiliriz. "Immediate Function" olarak da geçer. * Örnek 1, consteval int square(int x) { return x*x; } int main() { constexpr int x = square(5); // OK int ival = 5; square(ival); // ERROR: the value of ‘ival’ is not usable in a constant expression } > "constinit" Anahtar Sözcüğü: Bu anahtar sözcük ile tanımlanmış bir değişken, "static" olarak derleme zamanında "init." edilmiş bir değişkendir. Fakat bu konudan önce değişkenlerin hayata gelme aşamalarını irdeleyelim: -> Aynı kaynak dosyadaki "global" isim alanındaki değişkenler, bildirim sırasında göre hayata gelirler. Dil bu garantiyi vermektedir. Fakat farklı kaynak dosyalardaki "global" isim alanındaki değişkenlerden hangisinin ilk hayata geleceğini garanti eden bir mekanizma malesef yoktur. Örneğin, // ali.cpp A ax; // veli.cpp B bx; biçiminde iki adet kaynak dosyamız olsun. Bu değişkenlerden birisini, diğer sınıfın "Ctor." fonksiyonu içerisinde de pekala kullanabilirim. Dil, bu değişkenlerden hangisinin ilk hayata geleceğini garanti etmediğinden, felaket oluşacaktır. İşte bu duruma ise "Static Initialization (Order) Fiasco" denmektedir. İşte "constinit" anahtar sözcüğü bu problemin çözüm yollarından bir tanesidir. Detaylı bilgi için: https://www.jonathanmueller.dev/talk/static-initialization-order-fiasco/ -> "Storage Duration" ve "Lifetime/Life span" kavramları birbirleri ile ilişkili kavramlardır. Bunlardan ilki bir nesnenin yerinin nasıl ayrıldığını ayarlamaktadır. İkincisi ise nesne ne zaman hayata gelecek, ne zaman hayatı bitecek konularıyla alakalıdır. Burada "lifetime != storage duration". Örneğin, "static storage duration" a sahip nesneler için yer ayrılır fakat o nesnenin bulunduğu fonksiyon çağrılmadan ilgili "ctor." fonksiyonu çağrılmaz. Bir diğer deyişle "When does the lifetime of variables with static storage duration begins?" sorusunun cevabı nedir? Burada iki farklı kural devreye girmektedir. Bunlar, -> Global değişkenler iki kademe "init." ediliyorlar. Birinci kademeye "Static Init", ikinci kademeye "Dynamic Init." denmektedir. Buradaki ilk kademe derleme zamanında, ikinci kademe ise programın çalışma zamanında gerçekleştiriliyor. Buradaki ilk aşamada nesneye ilişkin bellek alanı sıfırlanıyor. Nesnenin asıl değerini aldığı süreç ise ikinci kademede gerçekleştiriliyor. Fakat öyle varlıklar var ki ikinci kademe bunlar için hiç uygulanmamaktadır. "static" ömürlü bir değişkeni ki bunlar "global" isim alanındaki değişken, "static" ömürlü yerel değişken veya "static" ömürlü sınıfların veri elemanı olabilir, bu anahtar sözcük ile tanımladığımızda, değişkenin "constant init." edilmesini garanti ediyoruz. Yani yukarıdaki "Dynamic Init." uygulanmamaktadır. Eğer "Dynamic Init." uygulanması gerekiyorsa da sentaks hatası meydana gelecektir. Bunu şu denklem ile de açıklayabiliriz: " constinit = constexpr - const " Anımsanacağı üzere "constexpr" değişkenler sabit ifadesi ile ilk değer almaları zorunluluktur. Öte yandan böyle değişken aynı zamanda "const" olduğu için değiştirilemezler de. İşte yine ilk değer alırken sabit ifadesinin kullanıldığı fakat "mutable" olan değişkenler için "constinit" anahtar sözcüğünü kullanmalıyız. * Örnek 1, #include constexpr int foo(int x) { return x*5; } constexpr auto x = foo(5); constinit auto y = foo(6); int main() { std::cout << "x : " << ++x << '\n'; // ERROR: increment of read-only variable ‘x’ std::cout << "y : " << ++y << '\n'; // OK } * Örnek 2, #include #include constexpr std::array get_array_4() { return { 10, 20, 30, 40 }; } constinit auto g_array_4 = get_array_4(); int main() { for(auto i : g_array_4) std::cout << i << ' '; // 10 20 30 40 std::cout << '\n'; // "g_array_4" IS NOT const g_array_4[0]++; g_array_4[1] += 1; for(auto i : g_array_4) std::cout << i << ' '; // 11 21 30 40 std::cout << '\n'; } * Örnek 3, #include #include #include template constexpr std::array get_array() { return std::array{0}; } constinit auto g_arr = get_array<10>(); int main() { for(auto i : g_arr) std::cout << i << ' '; std::cout << '\n'; // 0 0 0 0 0 0 0 0 0 0 std::for_each( begin(g_arr), end(g_arr), [](int& r){ ++r; } ); for(auto i : g_arr) std::cout << i << ' '; std::cout << '\n'; // 1 1 1 1 1 1 1 1 1 1 } > Hatırlatıcı Notlar: >> "https://www.jonathanmueller.dev/"