Merhaba arkadaşlar, bu yazımda daha önce std::optional ile başladığımız ve birbirleri ile ilintili olduğunu düşündüğüm ikinci kabiliyetten bahsedeceğim, std::variant.
std::optional ve benzeri diğer kabiliyet yazılarıma aşağıdaki bağlantılardan ulaşabilirsiniz:
- std::optional
- std::variant
- std::any
Geriye sadece std::any kalmış oluyor, o kabiliyete ayrı bir yazıda yer vereceğim. Peki std::variant nedir?
Tek bir cümle ile std::variant’ı özetleyecek olursak, “tip güvenli union’lar“, ibaresi union yapılarını bilenleriniz için yeterli olacaktır. union yapılarını bilmeyenleriniz için ise kısaca, union‘lara da bir göz atalım. Bu sayede std::variant gibi yapının nereden geldiğini ve hangi sıkıntılara derman olduğunu anlamamız daha kolay olacaktır diye umuyorum.
Birlik (“union”):
Aslında union (birlik) yapısı C programlama dili ile de sunulmaktaydı. Temel olarak bu yapı ile birlikte belirli ve sabit bir bellek alanına, farklı zamanlarda, farklı veri tiplerinde verileri girebilmeyi sağlayan yapıdır. Bunun ile birlikte bellekten de tasarruf sağlanabiliyordu. Bu yapıların nesnelerini oluşturabildiğimiz gibi işaretçilerini de oluşturabiliyoruz. Bu yapıyı oluşturan değişkenlere, isimleri ile erişebilirsiniz. Bildiğimiz struct yapılarından farklı olarak, union‘ı oluşturulan değişkenlerin her biri için yer ayrılmaz, ilgili değişkenlerin en büyüğü kadar yer ayrılır. Hemen bir örnek üzerinden bunu inceleyelim:
1 2 3 4 5 6 7 8 9 |
union ExampleUnion { char mCharValue; int mIntValue; float mFloatValue; }; union ExampleUnion instance; union ExampleUnion* ptr; |
Bu değişkenlerin herhangi birisi için veri girilebilir ama hepsi için girilemez. Bu sebeple eğer union kullanacaksanız bile, kullanılan tipi ayrıca bir değişken veya mekanizma ile takip etmelisiniz (ör. şu an float tutuluyor ya da int). Yukarıdaki örnek için aşağıdaki kullanım bu anlamda daha güvenli bir kullanım sağlayacaktır (buna “tagged unions” da deniliyor). Peki burada nasıl bir problem olabilir. Ör. instance nesnesi içerisindeki mCharValue değişkenine bir değer atadığınız zaman bu yapıdaki sadece bir byte kullanılmış olacak ve diğer üç byte boş/tanımsız kalacaktır. Bu da farklı değişkenlere erişimlerde beklenmeyen problemlere yol açabilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
struct ExampleUnion { union { char mCharValue; int mIntValue; float mFloatValue; }; // En son hangi tipi kullandigimizı tutalim enum { eCharType = 1, eIntType, eFloatType } mCurrentType; }; // Yukaridaki degiskenlere asagidaki gibi erisebilirsiniz instance.mCharValue = 'a'; instance.mIntValue = 1; instance.mFloatValue = 3.14; |
union ların yaygın olarak kullanıldığı durumlardan birisini de aşağıda görebilirsiniz:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Point3F { public: // Point ile ilgili kabiliyetler, etc. union { float mData[3]; struct { float mX; float mY; float mZ; }; }; }; Point3F position; // Ayni anda kullanilabilirler ve ayni anlama gelirler v.mData[0] = 1.0f; v., mX = 1.0f; |
C’den farklı olarak C++ union yapıları içerisinde private, pulic ve protected tanımlamaları yapılabilir. Ayrıca C++ 11 ile birlikte yapıcı/yıkıcı ve kopya yapıcı metotları veya atama operatörü içeren tipler de union‘lar ile birlikte kullanılabiliyorlar (ör. std::string).
Sonuç olarak union‘lar ile ilgili yaşadığımız/yaşayabileceğimiz problemleri aşağıdaki gibi sıralayabiliriz:
- İçerdiği veri tipinden farklı bir veri tipindeki değişkene erişmeye kalkarsanız, karşılaşacağınız davranış hoşunuza gitmeyebilir 🙂 Buna ilişkin durumları yukarıda gördük.
- İçerdiği veri tipini değiştirdiğiniz zaman ilgili nesneye ilişkin yapıcı veya yıkıcılar çağrılmaz. Hemen https://en.cppreference.com/w/cpp/language/union‘dan bir örnek üzerinden inceleyelim:
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 |
#include <iostream> #include <string> #include <vector> union CPlusPlusUnion { std::string str; std::vector<int> vec; // Burda hangi degiskenin yikicisi cagrilacak? ~S() { } }; int main() { S s = { "Hello, world" }; // Bu noktada olakı s.vec i okumaya kalkarsaniz, ne ile karsilacaginiz tanimsizdir std::cout << "s.str = " << s.str << '\n'; // Diger degiskeni kullanmadan once, bir oncekinin yikicisini cagirmalisiniz! s.str.~basic_string<char>(); // Sonra da yeni tipin yapicisini new (&s.vec) std::vector<int>; // Artik s.vec i kullanabilirsiniz. Pek kolay olmadi ne dersiniz :) s.vec.push_back(10); std::cout << s.vec.size() << '\n'; // Simdi artik bunu da el ile cagirmamiz gerekiyor s.vec.~vector<int>(); return 0; } |
Bu konu ile ilgili daha detaylı bilgi edinmek için kaynaklar kısmında verdiğim, referanslara bir göz atabilirsiniz.
Sonuç olarak std::variant, bellek belir bir miktarda yer kaplayan (ki bu alanı boost::variant‘ın aksine dinamik olarak almıyor) ve tanımlanan tipler arasında, bu alanın daha güvenli kullanılmasına olanak sağlayan mekanizma olarak özetleyebiliriz. Ayrıca eğer çok alt seviye bir kabiliyet geliştirmiyorsanız, union‘lar yerine std::variant‘ı tercih edebilirsiniz.
std::variant:
std::variant, programlama dünyasında, “sum type” denilen kavramın C++ dünyasındaki karşılığı. Ayrıca “tagged union”, “closed discriminated union” olarak da ifade edilebiliyor. Ne demek peki bu? Aslında kısaca (elbette arkasında oldukça derin bir teorik arka plan olduğuna eminim), aynı anda bir farklı tiplerden sadece birisini tutan tiplere verilen isim (ör. eğer bir tip aynı anda sadece A ya da B olabiliyor ise). Bunun bir benzerine de “product types” deniliyor. Bu da, birden fazla tipi içerisinde barındıran tiplere verilen isim (ör. struct, tuple, vb).
std::variant ile C++, belirlenmiş olan olası tip alternatiflerini içerebilen ve bunları daha güvenli bir şekilde kullanılmasını olanak sağlayan “sum type” mekanizmasını bizlere sunuyor.
std::variant, C++ 17 ile birlikte sunulmaya başlandı ve kullanmak için ‘<variant>’ başlık dosyasını eklemeniz gerekmekte. std::variant aslında arka planda şu anda aktif olarak kullanılan tipi tutar (bu anlamda ekstra bir bellek kullanımı vardır, ama tahmin edebileceğiniz üzere minicük), ayrıca içerisindeki değişkenlere ilişkin de indeksleme/numaralandırma yapılarak, bu değişkenlere bu indeksler üzerinde de erişim sağlanıyor (birazdan göreceğiz). Dinamik bellek kullanımı yapılmaz. Kullanılması olası bütün tipleri tanımlama sırasında belirlemeniz ve sağlamanız gerekiyor. Varsayılan olarak ilk tanımlanan tip aktif olarak atanır. Burada ayrıca aynı tipten birden fazla tutabilirsiniz (semantik olarak farklı anlamları ifade etmeleri için) ama boş std::variant tanımlamalarına ya da referans tipleri ya da dizi tiplerine izin verilmez (boş olarak tanımlamaya yönelik std::monostate yapısı tanımlanmış, bunun kullanımına da ileride değineceğim).
Peki hangi amaçlar için bu yapıyı kullanabiliriz. Burada bunları listeleyip, örnek kodları ise yazımın sonunda sizler ile paylaşacağım.
- Benim ilk aklıma gelen açıkçası, belirli bir alan için birden fazla tip kullanma ihtiyacı olan durumlarda: komut satırı parametrelerinin işlenmesi, konfigürasyon dosyalarının yönetilmesi, özellik atamaları vb.,
- Bir anlamda vtable kullanmadan, polimorfizmin sağlanması amacı ile kullanımı olabilir,
- “State machine” gerçeklenmesi için de ilginç bir kullanımı var. Bu kullanıma ilişkin detaylar için http://khuttun.github.io/2017/02/04/implementing-state-machines-with-std-variant.html ve https://www.youtube.com/watch?v=ojZbFIQSdl8 videosuna bir göz atabilirsiniz.
- Hataların döndürülmesi,
Kabiliyetlerin detaylarını sıralamadan önce hemen örnek bir kullanıma bakalım:
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 |
#include <variant> #include <iostream> int main() { // std::string aktif tip std::variant<int, std::string> varInstance{ "Selam" }; // Ciktisi 1 std::cout << varInstance.index() << '\n'; // Simdi artık aktif tip int varInstance = 42; // Cikti 0 std::cout << varInstance.index() << '\n'; ... try { // İlgili degere index ile erisim int i = std::get<0>(varInstance); // Su an aktif tip int oldugu için asagidaki kullanım exception atar auto s = std::get<std::string>(varInstance); // Yukaridaki satirin exception atmayan hali de mevcut elbette // Bu durumda std => nullptr auto str = std::get_if<std::string>(&var); ... } catch (const std::bad_variant_access& e) { // Hatali tipe erisim durumunda alacagımız hata std::cerr << "Hata: " << e.what() << '\n'; ... } } |
Kabiliyetler:
Şimdi std::optional‘da olduğu gibi, kalem kalem std::variant kullanımına ilişkin hususların üzerinden örnek kodlar ile birlikte geçelim:
- std::variant nesnelerini nasıl oluşturabiliriz:
- Öncelikli olarak std::variant varsayılan olarak sağlanan ilk elemanın, varsayılan yapıcısını çağırır ve onun tipi aktif olarak atanır,
1 2 |
// aktif tip int yapilir ve degeri 0 olarak atanir. Index de 0 dir std::variant<int, float, std::string> v1; |
-
- Ya da ilgili değeri geçirerek de nesneyi oluşturabilirsiniz. Bu durumda, değere en iyi karşılık gelen tip seçilir:
1 2 3 4 5 6 7 8 9 10 11 |
// Aktif tip float, degeri 4.14 ve index de 1 dir std::variant<int, float, std::string> instance1 { 4.14F}; // Hata: hangisi belli degil? 4.5 int'e de float'a da donusebilir std::variant<int, float, std::string> instance2{4.5}; // Hata: hangisi belli degil? std::variant<int, float> instance3{42.3}; // Bu tanimlamada cikarim yapilabilir ve aktif tip double olur std::variant<int, double> instance4{42.3}; |
-
- Eğer hangisinin seçileceği belli değil ise std::in_place_index ile açık bir şekilde hangi tipin seçilmesi gerektiği dikte edilebilir:
1 2 |
// Aktif tipin float olarak atanması dikte edilir ve yukarıdaki karışıklık giderilir std::variant<int, float, std::string> instance5 { std::in_place_index<1>, 4.5 }; |
-
- std::in_place_index ile “initialize list” nesnelerini de kullanabilirsiniz:
1 |
std::variant<std::vector<int>, int, std::string> instance6{std::in_place_index<0>,{4, 8, -7, -2, 0, 5}}; |
-
- Eğer daha karmaşık veri tipleri kullanıyorsanız, bu durumda da std::in_place yapısı da kullanılabilir:
1 2 3 4 5 6 7 8 |
// Hatalı kullanım. Birden fazlda ilklendirme degeri direk geçirilemez std::variant<std::complex<double>> instance5{3.0, 4.0}; // Hatalı kullanım. std::variant<std::complex<double>> instance6{{3.0, 4.0}}; // in_place_type/in_place_index ile birden fazla deger gecirilebilir std::variant<std::complex<double>> v11{std::in_place_type<std::complex<double>>, 3.0, 4.0}; std::variant<std::complex<double>> v12{std::in_place_index<0>, 3.0, 4.0}; |
-
- Varsayılan yapıcısı olmayan tipleri de desteklemek adına, std::monostate yapısı da bu kütüphane tarafından sunulmaktadır. Bu yapının temek amacı, std::variant nesnesinin, herhangi bir tipe ait bir değer içermediği durumları ifade etmektir. Nasıl yani? Mesela, varsayılan yapıcısı olmayan bir tipi de std::variant ile kullanmak istediğimizi düşünelim ve bu nesneyi de herhangi bir değer atamadan oluşturmamız gerekiyor ne yapacağız? Hemen bakalım:
1 2 3 4 5 6 7 8 9 10 11 12 |
struct NoDefConstr { NoDefConstr(int i) { std::cout << "NoDefConstr::NoDefConstr(int) called\n"; } }; // Hatalı kullanım. NoDefConstr varsayılan yapıcısı yok. std::variant<NoDefConstr, int> instanceError; // Burada NoDefConstr tipini de içerebilecek nesne olusturulur ve index de 0 olur std::variant<std::monostate, NoDefConstr, int> instanceOk; |
- std::variant tarafından tutulan değerlere nasıl erişebiliriz?
- Tutulan değerin tipini öğrenmek için std::holds_alternative<>() API’sini kullanabilirsiniz:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <variant> #include <string> #include <iostream> int main() { std::variant<int, std::string> v = "abc"; std::cout << std::boolalpha << "variant holds int? " << std::holds_alternative<int>(v) << '\n' << "variant holds string? " << std::holds_alternative<std::string>(v) << '\n'; } |
-
- İlgili değere ulaşmak için, alternatifin tipini girerek get<TYPE>() API’sini kullanabilirsiniz. Aynı zamanda tip yerine indexi de girebilirsiniz get<INDEX>(). Burada eğer ilgili tip tanımlı değil ise derleme zamanında hata, eğer mevcut atanmış tipten farklı bir tipte veri sorgulamaya kalkarsanız da çalışma zamanında hata (“exception”) fırlatılır.
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 <variant> #include <iostream> int main() { // Ornek variant nesnemiz. std::variant<int, float, std::string> var; // Duzgun kullanim mevcut int degeri (0) donulur. Iki kullanim da ayni auto intVal1 = std::get<int>(var); auto intVal2 = std::get<0>(var); // Hata: double tanimli degil auto dblValue = std::get<double>(var); // compile-time ERROR: no double // Hata: 5. alternatif tip tanimli degil auto nonValue= std::get<4>(var); try { // Su an aktif tip int oldugu icin hata firlatilir auto s = std::get<std::string>(var); // Problem yok. Gecerli kullanim auto i = std::get<0>(var); // Su an aktif tip indeksi 0 oldugu icin yine hata firlatilir auto j = std::get<1>(var); } catch (const std::bad_variant_access& e) { std::cout << "Exception: " << e.what() << '\n'; } return 0; } |
-
- get<>() API’si yanında hata fırlatmayan ve ilgili değere erişmeden kontrol yapmanıza olanak sağlayan get_if<>() API’sini de kullanabilirsiniz. Diğer API’den farkı ise bunun ilgili nesnenin işaretçisini parametre olarak alması:
1 2 3 4 5 6 7 8 9 |
// ip burada ilgili tipe işaretçiyi tutuyor ve hatalı durumlarda nullptr olarak atanıyor if (auto ip = std::get_if<1>(&var); ip) { std::cout << *ip << '\n'; } else { std::cout << "alternative with index 1 not set\n"; } |
- Evet değerlere nasıl eriştiğimize baktıktan sonra şimdi de nasıl değiştirebileceğimize bakalım:
- İlgili nesneye değeri atamak için atama operatörünü ya da emplace() API’sini kullanabilirsiniz. Ayrıca get ve get_if API’leri de referans ve işaretçi döndüğü için onlar da kullanılabilir:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Su an int aktif tip std::variant<int, float, std::string> instance; // Artik aktif tip std::string ve indeks 2 instance = "Merhaba"; // Artik aktif tip float ve indeks 1 instance.emplace<2>(3.5f); // Aktif tip float oldugu icin hata aliriz std::get<0>(var) = 77; // Herhangi bir sikinti yok std::get<1>(var) = 55.6f; // Aynı sekilde isaretciler üzerinde degisiklikte bir sıkıntı yok if (auto p = std::get_if<1>(&var); p) *p = 8.5f; |
- Ayrıca çok kritik olmasa da, aynı tip iki std::variant nesnesini karşılaştırabilirsiniz. Aktif tiplerin farklı olması durumunda hangisinin aktif olduğuna göre karşılaştırma operatörleri dönüş sağlar:
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 |
std::variant<std::monostate, int, std::string> v1; std::variant<std::monostate, int, std::string> v2{ "hello" }; std::variant<std::monostate, int, std::string> v3{ 42 }; std::variant<std::monostate, std::string, int> v4; // Derleme zamani hatasi (farkli tipler) v1 == v4 // COMPILE-TIME ERROR // false v1 == v2 // true v1 < v2 // yields true // true v1 < v3 // yields true // false v2 < v3 v1 = "hello"; // true v1 == v2 v2 = 41; // true v2 < v3 |
- std::variant‘ları kullandığınız durumlarda, ilgili nesnelerin barındırdıkları tiplere göre bazı işleri yapmak istersiniz. Bu işlevi de kolaylaştırmak adına STL’de bizlere std::visit API sini sunmakta. Bu API’nin temel amacı, kendisine geçirilen std::variant nesne/nesnelerini ilgili aktif tiplerine göre geçirilen fonksiyon tarafından çağrılması. Burada tabi ilgili fonksiyonun her bir tip için tanımlanmış olması önemli. Aksi halde derleme hatası alırsınız. Sanırım buna bir örnek üzerinden baksak daha iyi olacak 🙂
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 <variant> #include <string> #include <iostream> struct ExampleVisitor { void operator() (int i) const { std::cout << "int: " << i << '\n'; } void operator() (std::string s) const { std::cout << "string: " << s << '\n'; } void operator() (double d) const { std::cout << "double: " << d << '\n'; } }; int main() { std::variant<int, std::string, double> var(42); // MyVisitor'ın int için olanı çağrılır std::visit(ExampleVisitor(), var); var = "hello"; // MyVisitor'ın std::string için olanı cağrılır std::visit(ExampleVisitor(), var); var = 42.7; // MyVisitor'ın double için olanı çağrılır std::visit(ExampleVisitor(), var); } |
-
- visitor ile ayrıca ilgili nesnedeki aktif tipin değerini de değiştirebilirsiniz ama aktif tipi değiştiremezsiniz:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
struct Twice { void operator()(double& d) const { d *= 2; } void operator()(int& i) const { i *= 2; } void operator()(std::string& s) const { s = s + s; } }; std::visit(Twice(), var); |
-
- std::visit’in lambdalar ile de kullanımları mevcut. Burada sadece basit bir örnek verip, sizleri ilgili kaynaklara bakmaya davet ediyorum. Aşağıda basitçe ilgili nesnenin aktif tipine göre değeri standard çıktıya basan bir lambda örneğini görebilirsiniz. Benzer şekilde içeriği de değiştirebilirsiniz.
1 2 3 4 5 |
auto printvariant = [](const auto& val) { std::cout << val << '\n'; }; ... std::visit(printvariant, var); |
- std::variant ayrıca taşıma operatörleri (std::move) ile de kullanabilirsiniz,
- std::variant’lar her ne kadar bellekten ufak bir ödün versek de, çalışma zamanı performansında herhangi bir kayba yol açmıyor. Aşağıda ayrıca Ms Visual Studio 2017 için denediğim kod için aldığım boyut değerlerini görebilirsiniz:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <variant> #include <string> #include <iostream> int main() { std::cout << "sizeof string: " << sizeof(std::string) << "\n"; std::cout << "sizeof variant<int, string>: " << sizeof(std::variant<int, std::string>) << "\n"; std::cout << "sizeof variant<int, float>: " << sizeof(std::variant<int, float>) << "\n"; std::cout << "sizeof variant<int, double>: " << sizeof(std::variant<int, double>) << "\n"; return 0; } |
sizeof string: 40
sizeof variant<int, string>: 48
sizeof variant<int, float>: 8
sizeof variant<int, double>: 16
- Aklınızın bir köşesinde durması gereken son konu ise, alternatif tiplerin oluşturulması ya da güncellenmesi sırasında hata fırlatılması durumu. Bunu da yine güzel bir şekilde gösteren aşağıdaki örnek kod üzerinden inceleyelim.
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 |
// Hata fırtlatması için örnek bir durum olusturalim class ThrowingClass { public: explicit ThrowingClass(int i) { if (i == 0) throw int (10); } operator int () { throw int(10); } }; int main(int argc, char** argv) { std::variant<int, ThrowingClass> v; // Hatayı tetikleyelim: try { v = ThrowingClass(0); } catch (...) { std::cout << "catch(...)\n"; // Burada hata eski deger degisitirilmeden olustugu icin eski degere ulasip nesneyi kullanmaya devam edebiliriz. std::cout << v.valueless_by_exception() << "\n"; std::cout << std::get<int>(v) << "\n"; } // emplace() durumuna da bir goz atalım // Burada geri donus biraz zor try { // Yukaridaki durumdan farklı olarak emplace in dogasindan ötürü bir önceki aktif tip yok edildikten sonra hata fırlatıldıgı icin // eski degere ne yazıkki ulasamıyoruz. // Ayrica bu nesne de artik kullanilamaz v.emplace<0>(ThrowingClass(10)); // calls the operator int } catch (...) { std::cout << "catch(...)\n"; // Su an da tanımlı olmayan gecersiz bir durum basılır std::cout << v.valueless_by_exception() << "\n"; } return 0; } |
Yazımı tamamlamadan önce, çok temel bazı kullanımları içeren örnek kodları sizler ile paylaşacağım. Bunları Bartlomiej Filipek‘in sayfasından aldım, örnek kullanımları göstermesi açısından oldukça güzel. Bunlardan ilki poliformizme benzer bir mekanizmayı, miras mekanizmasını kullanmadan nasıl başarabileceğimiz gösteriyor:
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 <iostream> #include <vector> #include <variant> class Triangle { public: void Render() { std::cout << "Drawing a triangle!\n"; } }; class Polygon { public: void Render() { std::cout << "Drawing a polygon!\n"; } }; class Sphere { public: void Render() { std::cout << "Drawing a sphere!\n"; } }; int main() { std::vector<std::variant<Triangle, Polygon, Sphere>> objects{ Polygon(), Triangle(), Sphere(), Triangle() }; auto CallRender = [](auto& obj) { obj.Render(); }; for (auto& obj : objects) std::visit(CallRender, obj); } |
Bir diğeri ise hata durumlarının kotarılmasına ilişkin:
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 |
#include <iostream> #include <string> #include <variant> enum class ErrorCode { Ok, SystemError, IoError, NetworkError }; // Hata mi aldik yoksa ilgili string dogru bir sekilde geldi mi? std::variant<std::string, ErrorCode> FetchNameFromNetwork(int i) { if (i == 0) return ErrorCode::SystemError; if (i == 1) return ErrorCode::NetworkError; return std::string("Hello World!"); } int main() { // Sistem hatasi durumu auto response = FetchNameFromNetwork(0); if (std::holds_alternative<std::string>(response)) std::cout << std::get<std::string>(response) << "n"; else std::cout << "Error!\n"; // Duzgun bir sekilde stringin alinmasi durumu response = FetchNameFromNetwork(10); if (std::holds_alternative<std::string>(response)) std::cout << std::get<std::string>(response) << "n"; else std::cout << "Error!\n"; return 0; } |
Evet arkadaşlar, bu yazı ile birlikte std::variant maceramızın da sonuna geldik. Bir sonraki yazımda görüşmek dileğiyle.
Referanslar:
- https://en.cppreference.com/w/cpp/language/union
- http://www.informit.com/articles/article.aspx?p=31783&seqNum=2
- https://www.youtube.com/watch?v=k3O4EKX4z1c
- https://en.cppreference.com/w/cpp/utility/variant
- C++ 17 The Complete Guide, Nicolai M. Josuttis
- https://www.bfilipek.com/2018/06/variant.html
- https://pabloariasal.github.io/2018/06/26/std-variant/
- https://arne-mertz.de/2018/05/modern-c-features-stdvariant-and-stdvisit/
- https://dzone.com/articles/how-to-use-stdvisit-with-multiple-variants
- https://manishearth.github.io/blog/2017/03/04/what-are-sum-product-and-pi-types/