[:tr]Merhabalar arkadaşlar, yeni bir haftalık C++ yazımız ile birlikteyiz. Bu yazımın konusu, C++ 17 ile birlikte dile dahil edilen std::optional yeteneği. Bu kabiliyete neden ihtiyacımız var, nerelerde kullanabiliriz gibi sorulara çeşitli kod örnekleri üzerinden giderek bakacağız. Bu yapı ile ilintili olarak std::variant ve std::any yapılarına da farklı yazılarımda değineceğim. O zaman hemen başlayalım ne dersiniz.
Öncelikli olarak neden böyle bir yapıya ihtiyacımız var ona bakalım. Bazı durumlarda, değişkenlerde anlamlı bir veri olmadığını ifade etmek istediğiniz emin olmuştur (komut satırından girilen opsiyonel argümanlar ya da girilse de girilmese olabilecek alanlar, göbek ismi gibi). Mevcut durumda, bunu yapmanın elbette bir takım yolları var. Bunu işaretçilerde yapmak nispeten kolay, nullptr ile yapabilirsiniz ama bu ilklendirilmediği anlamına mı gelir ya da anlamlı veri olmadığına mı? Diğer tipler için durum biraz daha farklı. Tamsayı değişkenler için -1 ve benzeri değerler kullanabilirsiniz, bu durumda da artık -1 kullanamazsınız. Bazı durumlarda -1 gibi bir sayı bulmanız da zor olabilir ya da her seferinde bu amaç için kullanabileceğiniz bir rakam bulmak zor olabilir. Ör. Özel yapılar ya da ondalık sayılarda ne kullanacağız? Ayrıca bu tarz kullanımlarda, fazladan veri var mı ya da anlamlı mı, kontrolleri yapmanız gerekebilir (ör. önce ilgili değişken geçerli mi değil mi kontrolü, sonrasında eğer geçerli ise değişken değerini almak gibi).
İşte std::optional, tam da bu noktada imdadımıza yetişiyor. Bu yapı ile birlikte, ilgili değişkenin olup olmadığını/herhangi bir değer içerip içermediğini daha anlamlı ve standart bir şekilde gerçekleştirebileceğiz. Bu kabiliyet çeşitli kaynaklarda “nullable types” olarak da geçmektedir. Giriş kısmında anlattıklarımızı özetleyecek olursa, hiçbir değer almayacak tipleri ifade etmek için, bir işlem sonucunda anlamlı bir dönüş olmayacağı durumlarda ya da fonksiyonlara bir değer geçirme ya da geçirmeme durumları söz konusu olduğunda kullanılabilir.
std::optional, diğer bir takım kabiliyet gibi, boost::optional’ı baz almakta, o sebeple eğer onu kullandıysanız, geçişiniz çok zor olmayacaktır (bu arada boost kullanımına ilişkin örnek bir kaynak ekledim). İlgili kabiliyeti kullanmak için #include <optional> başlık dosyasını eklemeniz yeterli. std::optional ek olarak herhangi bir dinamik bellek kullanımı yapmaz, aslında basit bir “wrapper” olarak da değerlendirilebilir. Genelde ilgili yapının kendi kaplayacağı alan yanında, fazladan sadece bir byte kadar bir alan kullanır.
Peki, hangi durumlarda bu yapıyı kullanmalıyız? Güzel bir soru. Bu anlamda kalem kalem olası kullanım alanlarından bahsetmeden önce, boost::optional sayfasında, bu konuda verilen yazıya bir göz atabilirsiniz. İlgili sayfada daha detaylı bilgi verilse de, kısa bir özetini burada verelim:
optional<T> yi ilgili T değeri olmaması, T’ye ilişkin bir değer olması kadar normal, açık ve geçerli bir sebebin olduğu durumlarda kullanılması tavsiye edilmektedir.
Bu yapıyı bir sınıf üyesi olarak, dönüş değeri olarak ya da fonksiyona geçirilecek olan bir parametre olarak kullanabilirsiniz.
Detaylara geçmeden önce hemen bir örneğe bakalım. Bu örnek kod içerisinde string’i int’e çeviren bir metoda bakacağız:
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 | #include <optional> #include <string> #include <iostream> // Ilgili string'i mumkunse int'e cevirelim std::optional<int> convertToInt(const std::string& s) { try { return std::stoi(s); } catch (...) { return std::nullopt; } } int main() { for (auto s : {"42", " 077", "merhaba", "0x33"} ) { // ilgili degerleri cevir bakalim std::optional<int> oi = convertToInt(s); if (oi) { std::cout << "The integer obtained from '" << s << "' is: " << *oi << '\n'; } else { std::cout << "The integer can not be obtained from '" << s << "'\n"; } } return 0; } |
Şimdi kalem kalem std::optional kullanımına dair hususların üzerinden, örnek kodlar ile birlikte geçelim isterseniz. Daha sonra bu kabiliyetleri içeren daha kapsamlı kodlara da bakarız:
- Herhangi bir zamanda optional<T> nesnesi ya bir değer içerir ya da herhangi bir değer içermez ki bu da std::nullopt ile ifade edilir ki bu da aslında std::nullopt_t tipindedir.
- Nasıl oluşturabiliriz?
- Herhangi bir değer içermeyen nesneleri aşağıdaki gibi oluşturabiliriz. Aşağıdaki iki kullanımda bir değer içermez ve yapıcı çağrılmaz
1 2 3 | std::optional<int> inst1; std::optional<double> inst2(std::nullopt); |
- Bir değer veya farklı bir nesne ile oluşturmak için de aşağıdaki kullanımlar geçerlidir. Burada ayrıca tiplerden bahsetmeye gerek yok, std::optional bu çıkarımları yapabilir:
1 2 3 4 5 6 7 | // Burada => optional<int> std::optional intSample{42}; std::optional<std::string> strSample{"hello"}; // Burada => optional<const char*> std::optional charArrSample{"hello"}; |
- Birden fazla parametre alan nesneler için ilgili nesneyi oluşturup geçirebilirsiniz. Ya da daha güzeli direk ilgili nesne ile birlikte oluşturabilirsiniz, bu sayede geçici bir nesne oluşturulmasından da kurtulursunuz. Bunun için de std::in_place yapısı kullanılmakta:
1 2 3 | std::optional complexSample { std::complex { 3.0, 4.0 } }; std::optional<std::complex<double>> inPlaceInstance {std::in_place, 3.0, 4.0}; |
- Akıllı işaretçilerdeki gibi std::make_optional metodu da sunulmaktadır. Bu durumda, std::in_place kullanmanıza gerek yoktur:
1 2 3 | // optional<double> auto sampleOpt = std::make_optional(3.0); auto sampleOpt2 = std::make_optional<std::complex<double>>(3.0, 4.0); |
- Yine konteynerler ile de kullanılabilir:
1 | std::optional<std::vector<int>> vectorSample(std::in_place, {1, 2, 3, 4}); |
- Değere nasıl ulaşacağız?
- İlgili nesne içerisindeki değere ise value() ile ulaşabilirsiniz. Eğer ilgili nesne herhangi bir değer içermiyor ise o zaman std::bad_optional_access istisnası fırlatılır,
- Akıllı işaretçilere benzer kullanım da sunulmaktadır. * ve -> operatörler tanımlanmıştır ve bunlarda ilgili değerlere ulaşmak için kullanılabilirler. Bir önceki kullanımın aksine, eğer ilgili nesne bir değer içermiyor ise, o zaman herhangi bir istisna fırlatılmaz ve davranış tanımlı değildir. Yani dikkatli olmak lazım 🙂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | std::optional o{std::pair{42, "hello"}}; // p pair<int,string> nesnesini tutar auto p = *o; // 42 basılır std::cout << o->first; std::optional<std::string> o{"hello"}; // ”hello” basılır std::cout << *o; // ”hello” basılır std::cout << o.value(); // Nesneyi bosaltalim o = std::nullopt; // Tanımlı olmayan davranis std::cout << *o; |
- Son olarak value_or(varsayılanDeger) kullanımı da mevcut. Yani eğer ilgili nesne herhangi bir değer içermiyor ise varsayılanDeger dönülür.
1 2 3 4 5 | // Bos bir nesne olusturalim std::optional<double> odouble; // Bos oldugu icin varsayilan olarak ifade edilen deger donulur std::cout<< "odouble " << odouble.value_or(10.0) << '\n'; |
- Değer var mı nasıl sorgulayacağız?
- Herhangi bir std::optional nesnesinin bir veri içerip/içermediğini has_value() metodu ile sorgulayabilirsiniz.
- Ayrıca operator bool() da tanımlı olduğu için, nesnesinin kendisini if bloğunda kontrol edebilirsiniz.
- Aşağıda bu kullanımları görebilirsiniz:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // std::optional<int> std::optional o{15}; // true if (o) ... // false if (!o) ... // true if (o.has_value()) ... |
- Mevcut değeri nasıl değiştirebiliriz?
- Atama (=) operatörü ve emplace() API si ile bunu gerçekleştirebiliriz:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // o da henuz bir deger yok std::optional<std::complex<double>> inst; // optimal<int> tipi std::optional inst2{77}; inst = 42; // ilgili nesne tipi std::complex<double> olarak degisiyor inst = {9.9, 4.4}; // value becomes complex(9.9, 4.4) // Tip yine degisiyor :) inst = inst2; // Artik bir deger yok inst = std::nullopt; // Artik var inst.emplace(5.5, 7.7); // Yine yok inst = {}; |
- Ayrıca işaretçilerdeki gibi * operatörü ile de ilgili nesnelere değer atanabilir:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | std::optional<std::complex<double>> inst; // Hatali kullanim. Tanimli olmayan davranis *inst = 42; // Sikinti yok inst.emplace(2.2, 3.3); ... if (inst) { *inst = 88; *inst = {1.2, 3.4}; } |
- Atama yanında std::optional<> aynı zamanda taşıma mantıklarını da desteklemektedir. Bir diğer ifade ile std::move ile farklı bir nesne, bir diğer nesneye taşınabilir.
- Karşılaştırma operatörleri ile kullanabilir miyiz?
- Standart karşılaştırma operatörlerini nesneler ile kullanabilirsiniz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | std::optional<int> empty; std::optional<int> two(2); std::optional<int> four(4); // false / true basmak için std::cout << std::boolalpha; // true (four > two) std::cout << (four > two) << "\n"; // false (four < two) std::cout << (four < two) << "\n"; // false (four == two) std::cout << (four == 4) << "\n"; |
- Eğer her iki nesne içerisinde de değer yok ise == true döner. Diğer karşılaştırmaların hepsi false döner. Yalnız, nesnelerden birisi eğer std::nullopt ise yani yok ise farklı durumlar ortaya çıkabilir. İçerisinde değer olmayan nesne her zaman diğerlerinden az olarak kabul edilir:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | std::optional<int> empty; std::optional<int> two(2); // true empty == std::nullopt // false empty== 2 // true empty < two // false empty > two // true two == 2 // true empty < two |
- std::optional metot dönüş değeri olarak nasıl kullanılır? Yazının başında verdiğimiz kod parçası bu anlamda aslında güzel bir örnek.
- reset() metodu ile de nesne içerisindeki tutulan değer atılır ve boş hale getirilir.
Şimdi çok temel bazı kullanımları içeren örnek kodlara bakalım. Bunların ilki, referanslarda verdiğim bir kitaptan aldığım kod:
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 | #include <optional> #include <iostream> using namespace std; class Name { private: string mFirst; // optional'in uye olarak kullanilmasi optional<string> mMiddle; string mLast; public: // optional'in parametre olarak gecirilmesi ve move semantics icin ornek kullanim Name(string f, optional<string> m, string l) : mFirst{ move(f) }, mMiddle{ move(m) }, mLast{ move(l) } { } friend ostream& operator << (ostream& strm, const Name& n); { strm << n.mFirst << ' '; if (n.mMiddle) { strm << *n.mMiddle << ' '; } return strm << n.mLast; } }; int main() { // Gobek ismi yok Name n { "Ayse", nullopt, "Kopar" }; cout << n << '\n'; // Gobek ismi var Name m{ "Ahmet", "Ekrem", "Uzar" }; cout << m << '\n'; } |
Diğeri de ilgili referans sayfasından. Bu iki örnek de sizlere std::optional kullanımı hakkında sizlere fikir verecektir.
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 | #include <string> #include <functional> #include <iostream> #include <optional> // Factory benzeri yapilardan donus icin kullanima ornek std::optional<std::string> create(bool b) { if (b) return "Godzilla"; return {}; } // auto donus degeri ile kullanima ornek auto create2(bool b) { return b ? std::optional<std::string>{"Godzilla"} : std::nullopt; } // referans tipinde donus saglamak icin std::reference_wrapper kullanilabilir auto create_ref(bool b) { static std::string value = "Godzilla"; return b ? std::optional<std::reference_wrapper<std::string>>{value} : std::nullopt; } int main() { std::cout << "create(false) returned " << create(false).value_or("empty") << '\n'; // Yukaridaki metotlar ile donulen degerleri kosul kontrolleri icin kullanabiliriz if (auto str = create2(true); str) { std::cout << "create2(true) returned " << *str << '\n'; } if (auto str = create_ref(true); str) { // get() API si ile reference_wrapper tarafindan tutulan degere erisim std::cout << "create_ref(true) returned " << str->get() << '\n'; str->get() = "Mothra"; std::cout << "modifying it changed it to " << str->get() << '\n'; } } |
Bir sonraki yazımda görüşmek dileğiyle. Bol kodlu günler:
Kaynaklar:
https://en.cppreference.com/w/cpp/utility/optional
https://arne-mertz.de/2018/06/modern-c-features-stdoptional/
C++ 17 The Complete Guide, Nicolai M. Josuttis
https://www.fluentcpp.com/2016/11/24/clearer-interfaces-with-optionalt/[:en]Hello, my friends, we are together with another weekly C++ post. The topic of this post is about std::optional which is introduced with C++ 17. In this post, we are going to take alook at why we need such capability an in which cases we can use that with example codes. This post will be followed by std::variant and std::any topics which are also related with this topic.
First of all, let us look at why we need such feature. I am sure, at one point in your code, you needed to state whether a given variable contains value or not (i.e. middle names or optional arguments provided via command line). Of course, there are solutions that can be used to deal with this problem. For instance, for pointers, you may use nullptr to state but it still does not give much information about whether given pointer is not allocated or does not contain any valid value, right? This is a little bit different for other types. -1 or similar values can be used for integral values, but in that case, -1 can not be used anymore. It may also not be possible to come up with such numbers or not suitable (i.e. for floating numbers). Besides, in those cases, you usually required to perform additional checks.
std::optional, is introduced to resolve such issues. With this feature, we are able to check if a value is available or not in a more standard and expressive manner. Such types are also known as nullable typesin some references. Similarly, this can be used for returning values and passing parameters to functions which may or may not be available.
Most of the std::optional features are based on boost::optional, so if you are familiar with that library, you can easily adapt std::optional in your codes (by the way I have added an example reference for boost::optional usage). To use std::optional, you need to add #include <optional> header file. std::optional does not allocate memory from heap, in fact, it is simply a wrapper around the type provided. At most, one additional byte is allocated to specify availability.
So, afterall, in which cases we should use this feature? Good question. Let us look at a part of explanation given in boost::optional page, to see the full description you can check out this page.:
It is recommended to useoptional<T>in situations where there is exactly one, clear (to all parties) reason for having no value of typeT, and where the lack of value is as natural as having any regular value ofT.
You can use std::optional as class member, return value or parameter that will be passed to functions. Before delving into details and other examples, let us look at a simple example code that uses std::optional for a function that convert (if possible) string to integer:
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 | #include <optional> #include <string> #include <iostream> // Convert string to int if possible std::optional<int> convertToInt(const std::string& s) { try { return std::stoi(s); } catch (...) { return std::nullopt; } } int main() { for (auto s : {"42", " 077", "merhaba", "0x33"} ) { // Convert the string to int std::optional<int> oi = convertToInt(s); if (oi) { std::cout << "The integer obtained from '" << s << "' is: " << *oi << '\n'; } else { std::cout << "The integer can not be obtained from '" << s << "'\n"; } } return 0; } |
Now, let us go over individual capabilities of std::optional usage one by one with example source code snippets. Later, we will look at complete code examples:
- At any time, an instance of optional<T> may either contain a T value or does not contain any value at all which is express with std::nullopt (type of std::nullopt_t).
- How can we create an instance of optional<T>?
- We can create std::optional instances that do not contain any values as follows. Both of following instances do not contain any value and no contructor is called:
1 2 3 | std::optional<int> inst1; std::optional<double> inst2(std::nullopt); |
- We can create std::optional instances with given values or instances as follow. std::optional can also deduce types from provided parameters:
1 2 3 4 5 6 7 | // Here instance is => optional<int> std::optional intSample{42}; std::optional<std::string> strSample{"hello"}; // Here instance is => optional<const char*> std::optional charArrSample{"hello"}; |
- The instances that require multiple parameters, can be separately created and passed to std::optional instances which may cause temporary copy operations. The std::optional instances can directly be created with given multiple parameters (like emplace_back in STL containers) which prevent temporary copy operations. std::in_place is used for this purpose:
1 2 3 | std::optional complexSample { std::complex { 3.0, 4.0 } }; std::optional<std::complex<double>> inPlaceInstance {std::in_place, 3.0, 4.0}; |
- std::make_optional method can also be used for instantiation of std::optional objects like in smart pointers. In this case, std::in_place is not required:
1 2 3 | // optional<double> auto sampleOpt = std::make_optional(3.0); auto sampleOpt2 = std::make_optional<std::complex<double>>(3.0, 4.0); |
- std::optional can also be used with STL containers:
1 | std::optional<std::vector<int>> vectorSample(std::in_place, {1, 2, 3, 4}); |
- How can we access the contained values?
- value() API can be used to access the value contained in std::optional instance. If given instance does not contain any value, then std::bad_optional_access exception is thrown,
- In addition to value() API, pointer operators such as * and -> can also be used with std::optional instances. Contrary to previous usage, if instance does not contain any value, then no exception is thrown and behavior is undefined. So you have to be careful 🙂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | std::optional o{std::pair{42, "hello"}}; // p contains pair<int,string> instance auto p = *o; // 42 is printed std::cout << o->first; std::optional<std::string> o{"hello"}; // ”hello” is printed std::cout << *o; // ”hello” is printed std::cout << o.value(); // Clear the instance o = std::nullopt; // Undefined behavior! std::cout << *o; |
- Lastly, value_or(defaultValue) API is also provided with optional library which returns defaultValue if given std::optional instance does not contain any value.
1 2 3 4 5 | // Create an empty instance std::optional<double> odouble; // Here 10.0, the returned default value, is printed std::cout<< "odouble " << odouble.value_or(10.0) << '\n'; |
- How can we check if given std::optional instance contain any value?
- has_value() API can be used to query if given instance contain any value,
- operator bool() is also defined and can be used with conditional statements such as if-else if,
- Example usages are provided below:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // std::optional<int> std::optional o{15}; // true if (o) ... // false if (!o) ... // true if (o.has_value()) ... |
- How can we update existing value?
- Assignment operator (=) ve emplace() API can be used for this purpose:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // No value is available for this instance std::optional<std::complex<double>> inst; // optimal<int> type std::optional inst2{77}; inst = 42; // the type contained in std::optional is updated as std::complex<double> inst = {9.9, 4.4}; // Type is chnaged again :) inst = inst2; // No more value is contained inst = std::nullopt; // Now it contains inst.emplace(5.5, 7.7); // Not anymore :) inst = {}; |
- * operator can also be used for assignment, but the given std::optional instance should contain a value:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | std::optional<std::complex<double>> inst; // Incorect usage. Undefined behavior *inst = 42; // No problem inst.emplace(2.2, 3.3); ... if (inst) { *inst = 88; *inst = {1.2, 3.4}; } |
- std::optional<> also support move semantics. In other words, std::move can be used to transfer instances from other instances.
- Can we use comparison operators?
- You can use standard comparison operators with std::optional instances.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | std::optional<int> empty; std::optional<int> two(2); std::optional<int> four(4); // To print false / true std::cout << std::boolalpha; // true (four > two) std::cout << (four > two) << "\n"; // false (four < two) std::cout << (four < two) << "\n"; // false (four == two) std::cout << (four == 4) << "\n"; |
- If both of instances do no contain any value, == comparison returns true. If one instance does not contain any value, then different results can be observed such that no value instance always considered less than other instance that contain value. Let us look at example usages:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | std::optional<int> empty; std::optional<int> two(2); // true empty == std::nullopt // false empty== 2 // true empty < two // false empty > two // true two == 2 // true empty < two |
- How std::optional can be used for function returns is given in the sample code that is provided at the beginning of my post.
- reset() API can be used to reset/clear the value contained in given instance.
It is time to look at two complete code examples which illustrate basic std::optional usages. I took first example from the book that I gave in references section:
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 | #include <optional> #include <iostream> using namespace std; class Name { private: string mFirst; // Example use of optional as class member optional<string> mMiddle; string mLast; public: // Example use of optional as function parameter and move semantics Name(string f, optional<string> m, string l) : mFirst{ move(f) }, mMiddle{ move(m) }, mLast{ move(l) } { } friend ostream& operator << (ostream& strm, const Name& n); { strm << n.mFirst << ' '; if (n.mMiddle) { strm << *n.mMiddle << ' '; } return strm << n.mLast; } }; int main() { // No middle name Name n { "Ayse", nullopt, "Kopar" }; cout << n << '\n'; // Have middle name Name m{ "Ahmet", "Ekrem", "Uzar" }; cout << m << '\n'; } |
The other example code is taken from CPP reference page. I hope these two example codes help you to understand std::optional better.
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 | #include <string> #include <functional> #include <iostream> #include <optional> // Example usage for factory like methods std::optional<std::string> create(bool b) { if (b) return "Godzilla"; return {}; } // returning as auto values auto create2(bool b) { return b ? std::optional<std::string>{"Godzilla"} : std::nullopt; } // returning as reference type via std::reference_wrapper usage auto create_ref(bool b) { static std::string value = "Godzilla"; return b ? std::optional<std::reference_wrapper<std::string>>{value} : std::nullopt; } int main() { std::cout << "create(false) returned " << create(false).value_or("empty") << '\n'; // The returned std::optional instance can be used for conditional statements if (auto str = create2(true); str) { std::cout << "create2(true) returned " << *str << '\n'; } if (auto str = create_ref(true); str) { // get() API can be used to access the value reside at reference_wrapper std::cout << "create_ref(true) returned " << str->get() << '\n'; str->get() = "Mothra"; std::cout << "modifying it changed it to " << str->get() << '\n'; } } |
See you in my next post. Have plenty of coding 🙂
References:
https://en.cppreference.com/w/cpp/utility/optional
https://arne-mertz.de/2018/06/modern-c-features-stdoptional/
C++ 17 The Complete Guide, Nicolai M. Josuttis
https://www.fluentcpp.com/2016/11/24/clearer-interfaces-with-optionalt/[:]

Harika Bir Tutorial Olmuş. Emeğine Sağlık
Güzel geri bildiriminiz için teşekkür ediyorum, faydalı olduysa ne ala.
Bez necə yazım
Bunlar hamısı
*