Merhaba sevgili yazılımperver dostlarım,
C++ 14 yazılarımıza devam ediyoruz. Bu yazımda, mevcut kabiliyetlere yapılan bir takım ekleme mahiyetindeki özelliğe göz atıyor olacağız. Her ne kadar, çok sık kullanacağınız ya da muhakkak ihtiyacınız olacak kabiliyetler olmasa da, yine de faydalı olacağına inanıyorum. O zaman daha fazla oyalanmadan bunlara bir göz atalım.
constexpr fonksiyonlara ilişkin güncellemeler
constexpr fonksiyonlara ilişkin güncellemelere değinmeden önce elbette C++ 11 ile sunulan kabiliyetlere bakmakta fayda var. Daha detaylı bir hatırlama için aşağıdaki yazıma göz atabilirsiniz. Bu yazım içerisinde constexpr fonksiyonlar yanında, diğer tanımlamalara ve kısıtlara değinmiştim.
Modern C++ (3): Uniform Initialization, override/final, default/delete, constexpr, vb.
Özetleyecek olursak. constexpr fonksiyonlar bize derleme zamanında koşturulabilen ve hesaplamaları da derleme zamanında yapmanıza olanak sağlayan bir mekanizma sunmaktadır.
Peki, bu kabiliyete ilişkin C++ 11’deki kısıtlar neydi? Bir kere virtual olamaz, muhakak argüman içermeli, dönüş değeri, constexpr tanımlamalar ile kullanılabilen “literal type” olmalıdır. Tek bir dönüş ifadesi olmalıdır. Koşul ya da döngü ifadeleri de içeremez (koşul (“Ternary”) operatörü ile koşul kısmını halledebiliyorduk gerçi ama peki şık olmayabilirdi).
Hemen bir örneğe göz atalım isterseniz:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> constexpr int Multiply(int x, int y) { return (x * y); } int main() { const int x = Multiply(10, 20); std::cout << x; return 0; } |
Yukarıdaki örnekte verilen fonksiyon ile çarpma işlemlerini derleme zamanında yapabiliyoruz. Gelelim C++ 14’e.
C++ 14 ile birlikte artık aşağıdakileri de yapabiliyoruz:
- Fonksiyon içerisinde statik ve ilklendirilmemiş değişkenler dışında, değişken tanımlama,
- if ve switch koşul kontrolleri (goto kullanamıyorsunuz),
- Döngüler (for, while, do-while ve range-based),
- Birden fazla ifade kullanımı,
- Başka constexpr fonksiyon kullanımı.
Şimdi hemen C++ 14 ile sunulan bu özelliğe ilişkin bir örneğe bakalım. Bunun için de faktöryel hesaplayan bir fonksiyonu inceleyelim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> constexpr int CalculateFactorial(int n) { int value = 1; do { value *= n; } while (--n); return value; } int main() { // auto result = CalculateFactorial(5); constexpr auto result = CalculateFactorial(5); std::cout << "Derleme Zamanı Sonucu: " << result << std::endl; } |
Yukarıdaki kodu C++ 11 derleyicisi ile derlediğinizde, aşağıdaki hatayı ya da benzerini alırsınız:
1 2 3 4 |
In function 'constexpr int CalculateFactorial(int)': 10:1: error: body of constexpr function 'constexpr int CalculateFactorial(int)' not a return-statement In function 'int main()': 13:49: error: 'constexpr int CalculateFactorial(int)' called in a constant expression |
Şimdi aynı kodu C++ 14 derleyicisinde derleyin ve sonucu görün ki bu 120 olmalı.
Burada, ilgili kod tamamen derleme zamanında hesaplanmaktadır. Peki bu bütün constexpr fonksiyonlar için geçerli mi?
Elbette hayır. Burada dikkat edilmesi gereken husus, geçirilen parametrelerin const olması ve dönüş değerinin de yine aynı şekilde const bir ifade de kullanılmasıdır. Arkada neler döndüğünü görmek için yukarıdaki kodu “Compiler Explorer“‘a aktarıp, önce mevcut hali ile sonra da, yorumlu kısmı açıp, alt satırı yoruma alarak görebilirsiniz 😉
Bu kabiliyete ilişkin yapılan değişiklikleri daha detaylı görmek için, kaynaklar kısmına eklediğim ve değişiklikleri açık bir şekilde gösteren dokümana da göz atabilirsiniz.
Fonskiyonlar için dönüşü tipi çıkarımı (Return type deduction)
C++ 14 ile iyileştirme sağlanan bir diğer kabiliyet de, fonksiyonların dönüş değerinin çıkarılması. Bunu dediğimde aklınızda hemen şöyle bir soru belirmiş olabilir: C++ 11 ile bunu nasıl sağlıyorduk? Öncelikle elbette C++ 14’ün sunduğu kadar kolay ve temiz şekilde olmasada, bir seviyede sağlanmaktaydı. Bu da, lambda ve template fonksiyonları ile sağlanıyordu. Önce lambda fonksiyonlara bakalım:
1 2 |
// C++11'de geçerli [=] { return func() * 42; } |
Bir diğer örnek kullanım da, template fonksiyonlar aracılığı ile oluyordu. Fakat dönüş değerini belirlemek için aşağıdaki gibi bir takla atmanız gerekiyordu:
1 2 3 4 |
template <typename A, typename B> auto exampleFunc(const A& left, const B& right) -> decltype(left.exampleFunc(right)) { return left.exampleFunc(right); } |
bir diğer ifade ile, dönüş değerini derleyiciye söylemek için “decltype” ibaresini kullanmanız gerekiyor. Peki, bunu C++ 14 ile artık nasıl yapabiliyoruz? Eminim hepiniz tahmin etmişsinizdir:
1 2 3 4 |
template <typename A, typename B> auto exampleFunc(const A& left, const B& right) { return left.exampleFunc(right); } |
Tabi ki bu özellik sadece template fonksiyonlar için geçerli değil, normal fonksiyonlar için de bunu kullanabiliyoruz ve dahi recursive fonksiyonları da bu şekilde tanımlayabiliyoruz. Hemen basit örneklere bir göz atalım:
1 2 3 4 5 6 7 8 |
// f int dönüyor auto f() { return 42; } // g hiç bir şey dönmüyor yani void auto g() { std::cout << "Merhaba dunya!\n"; } // h std::string dönüyor auto h() { return "Merhaba dunya!"; } |
Tabi bir takım kısıtlamalar da yok değil:
- Öncelikle dönüş değeri auto olan fonksiyon içerisindeki bütün dönüş değerleri aynı tip olmalıdır.
-
1234567Aşağıdaki kullanım geçerli bir kullanım değil:auto someFunc(bool b) {if (b)return 5; // int dönüyorelsereturn 6.7; // double dönüyor, neler oluyor burada !??}
-
- Recursive fonksiyon tanımlamalarında da, recursive çağrı öncesinde, bir dönüş yapmanız gerekiyor ama bu çok da zor olmasa gerek 😉
-
123456auto factorial(int N) {// Eger bu donus ifadesi sonra gelseydi asagidaki kod derlenmezdiif(N == 0 || N == 1)return 1;return N * factorial (N – 1);}
-
Evet sevgili yazılımperver dostlarım bir C++ yazımızın daha sonuna geldik, bir sonraki yazımda görüşmek dileğiyle bol kodlu günler diliyorum hepinize.