> "std::variant" : C dilindeki "union" yapısının C++ diline uyarlanmış halidir. Tıpkı "std::optional" gibi "std::variant" da bir "Value Type" dır. Şablon parametresi olan türlerden birisini tutmaktadır. Buradaki şablon parametreleri aynı tür olabilir fakat bir "t" anında bunlardan sadece bir tanesini tutmaktadır. Doğrudan "nullable" tür değildir, fakat bir takım yöntemler ile "nullable" hale getirebiliriz. Kendi bünyesinde tuttuğu nesne için dinamik bellek yönetimi uygulamamaktadır. Fakat o nesne kendi bünyesinde dinamik bellek yönetimi uygulayabilir. Son olarak kalıtıma alternatif olarak da kullanabiliriz. * Örnek 1, #include #include #include class Myclass { public: Myclass(int) { /* "Default Ctor." is not declared!.. */ } }; int main() { { /* * 1-) "Default Init." yapılan "std::variant" nesnesi * daima ilk alternatifi tutacaktır. Bu durumda * ilgili nesnemiz bünyesinde "int" tutmaktadır. */ std::variant va; } { /* * 2-) Eğer ilk alternatif "Default Init." edilemezse, * sentaks hatası oluşacaktır. */ std::variant va; } { /* * 3-) Böylesi durumlar için ilk alternatifi "std::monostate" * türü yapabiliriz. */ std::variant va; } } * Örnek 2, #include #include #include class Myclass { public: Myclass(int) { /* "Default Ctor." is not declared!.. */ } }; int main() { { /* * 1-) İlk değer verdiğimiz ifadenin türü ya alternatif * türlerden birisi ya da o türlere dönüştürülebilir bir * tür olmalıdır. */ std::variant va{ 12 }; std::variant vaa{ 1.2 }; std::variant vaaa{ 'a'}; } { /* * 2-) Fakat bu durumda "ambiguity" meydana gelebilir. */ std::variant va{ 3.4 }; } { /* * 3-) Diğer yandan, tıpkı "Function Overload Resolution" sırasında * gerçekleştiği gibi; "float" türünden "double" türüne dönüşüm en * yüksek öneme sahip olduğundan, ilk değer olarak "double" tutulacaktır. */ std::variant va{ 1.2f }; } } * Örnek 3, #include #include #include void what_is_holded(const std::variant& va); int main() { { /* * 1-) Tabii bünyesinde hangi türün tuttuğunu öğrenmek için * ".index()" fonksiyonunu çağırmalıyız. */ std::variant va{}; std::cout << "Type: " << va.index() << "\n"; va = 12; std::cout << "Type: " << va.index() << "\n"; va = 1.2; std::cout << "Type: " << va.index() << "\n"; va = 12; std::cout << "Type: " << va.index() << "\n"; va = 'a'; std::cout << "Type: " << va.index() << "\n"; } { /* * 2-) Fakat C++20 öncesinde eğer alternatifler "bool" ve "std::string" * ise "bool" olanın indeksi ilgili fonksiyon tarafından geri döndürülmektedir. * Şüphesiz "vx" nesnesine "ulya" yerine "ulya"s geçilseydi, direkt olarak * "std::string" olanın indis bilgisini elde edecektik. */ std::variant vx("ulya"); std::cout << "Index : " << vx.index() << "\n"; } { /* * 3-) Bir diğer alternatif fonksiyon ise "hold_alternatif()" * fonksiyonudur. */ std::variant va{}; what_is_holded(va); va = 12; what_is_holded(va); va = 1.2; what_is_holded(va); } } void what_is_holded(const std::variant& va) { if (std::holds_alternative(va)) { std::cout << std::boolalpha << "A char is being holded!..\n"; } else if (std::holds_alternative(va)) { std::cout << std::boolalpha << "An int is being holded!..\n"; } else { std::cout << std::boolalpha << "A double is being holded!..\n"; } } * Örnek 4, #include #include #include int main() { { /* * 1-) İlk değer verirken parametreleri "perfect forward" etmek istiyorsak * "std::in_place_index" nesnesini kullanmalıyız. */ std::variant va{ std::in_place_index<2>, 10, 'a'}; } { /* * 2-) Bunun bir diğer alternatifi ise "std::in_place_type" nesnesini kullanmaktır. */ std::variant va{ std::in_place_type, 10, 'a'}; } { /* * 3-) Bu iki nesnesi kullanarak da "ambiguity" hatasını giderebiliriz. */ std::variant va{ 34u }; // Ambiguity std::variant vaa{ std::in_place_index<2>, 34u }; // OK std::variant vaa{ std::in_place_type, 34u }; // OK std::variant vb{ 12 }; // Ambiguity std::variant vb{ std::in_place_index<1>, 12 }; // OK } } * Örnek 5, #include #include #include class Myclass { public: Myclass() { std::cout << "Default Ctor.\n"; } Myclass(const Myclass&) { std::cout << "Copy Ctor.\n"; } Myclass(Myclass&&) { std::cout << "Move Ctor.\n"; } }; int main() { { std::variant vaa; std::variant vaaa{ std::in_place_index<1> }; // Default Ctor. std::variant va; // Default Ctor. std::variant vb{ Myclass{} }; // Default Ctor. & Move Ctor. std::variant vbb{ std::in_place_index<1> }; // Default Ctor. std::variant vbbb{ std::in_place_type }; // Default Ctor. } } * Örnek 6, #include #include #include struct Nec { int x, y; }; struct Erg { double x, y; }; struct Buffer { unsigned char len[256]; }; using var_type = std::variant; using var_type_custom = std::variant; int main() { { /* # OUTPUT # Sizeof Nec : 8 Sizeof Erg : 16 Sizeof Buffer : 256 Sizeof var_type : 16 Sizeof var_type_custom : 264 */ std::cout << "Sizeof Nec : " << sizeof(Nec) << "\n"; std::cout << "Sizeof Erg : " << sizeof(Erg) << "\n"; std::cout << "Sizeof Buffer : " << sizeof(Buffer) << "\n"; std::cout << "Sizeof var_type : " << sizeof(var_type) << "\n"; std::cout << "Sizeof var_type_custom : " << sizeof(var_type_custom) << "\n"; } } * Örnek 7, #include #include #include void what_to_have(const std::variant& va); int main() { { /* * 1-) "std::get()" fonksiyonu ile "std::variant" * içerisinde tutulan değere erişebiliriz. */ std::variant vx{ 4.5 }; std::cout << std::get<1>(vx) << "\n"; } { /* * 2-) Fakat bu fonksiyona geçilen indeks bilgisi * eğer "std::variant" nesnesinin tuttuğu değere * ilişkin değilse, bir "exception" gönderilecektir. */ std::variant vx{ 4.5 }; try { std::cout << std::get<2>(vx) << "\n"; } catch (const std::exception& ex) { std::cout << ex.what() << "\n"; } } { /* * 3-) Fakat bu fonksiyona geçilen indeks bilgisi * geçersiz bir indeks ise sentaks hatası oluşacaktır. */ std::variant vx{ 4.5 }; // std::cout << std::get<4>(vx) << "\n"; // ERROR } { /* * 4-) Bu fonksiyon, "std::variant" içerisindeki nesneye * referans döndürmektedir. */ std::variant vx{ 4.5 }; std::cout << std::get<1>(vx) << "\n"; std::get<1>(vx) = 5.4; std::cout << std::get<1>(vx) << "\n"; } { /* * 5-) "std::get()" fonksiyonunun alternatifi ise "std::get_if()" * fonksiyonudur. Bu fonksiyon "exception" fırlatmıyor ve "pointer" * semantiğiyle birlikte kullanmalıyız. Eğer geçersiz bir indeks * girersek de "nullptr" değerini döndürmektedir. */ std::variant vx{ "Ulya" }; what_to_have(vx); vx = 1.2; what_to_have(vx); vx = 12; what_to_have(vx); } } void what_to_have(const std::variant& va) { if (va.index() == 0) { std::cout << "Value: " << std::get<0>(va) << "\n"; } if (std::holds_alternative(va)) { std::cout << "Value: " << std::get<1>(va) << "\n"; } if (auto ptr{std::get_if(&va)}; ptr) { std::cout << "Value: " << *ptr << "\n"; } } * Örnek 8, #include #include #include int main() { { /* * 1-) Okumayı kolaylaştırmak adına bir takım "using" * bildirimleri de oluşturulmaktadır. */ enum index : size_t { age, wage, name }; using Age = int; using Wage = double; using Name = std::string; std::variant vx(45); std::cout << "Age : " << std::get(vx) << "\n"; vx = 1'000'000.00'987; std::cout << "Wage: " << std::get(vx) << "\n"; vx = "Ulya Yuruk"; std::cout << "Name: " << std::get(vx) << "\n"; } } * Örnek 9, #include #include #include class Myclass { public: Myclass(int a, double d) { std::cout << "a : " << a << ", d : " << d << "\n"; } void print(void) const { std::cout << "Myclass::print()\n"; } ~Myclass() { std::cout << "Dtor.\n"; } }; int main() { { /* * 1-) "std::variant" nesnesine değer atamanın bir diğer * alternatifi ise ".emplace()" fonksiyonunu çağırmaktır. * Bu fonksiyon da bir şablon olduğundan, şablon parametresini * belirtmeliyiz. Bu fonksiyon "Perfect Forwarding" yapabildiği * gibi önceki değeri de silmektedir. */ std::variant vx; vx.emplace(Myclass{5, 5.f}); std::get(vx).print(); vx.emplace(1.2); std::cout << std::get(vx) << "\n"; vx.emplace(12); std::cout << std::get(vx) << "\n"; } } * Örnek 10, #include #include #include struct A { A(int) { //... } }; struct B { B(int) { } }; int main() { { /* * 1-) Eğer "std::variant" nesnesinin alternatifleri * "Default Init." yapılamıyorsa sentaks hatası alacağız. * Eğer alternatifler arasında "std::monostate" sınıfını * kullanırsak hatayı gidermiş olacağız. Her ne kadar bu * sınıfın ilk alternatif olması bir zorunluluk değilse de, * tipik kullanımda ilk alternatiftir. */ // std::variant va; // ERROR // std::variant vb; // ERROR std::variant vc; // OK } { /* * 2-) "std::monostate" sınıfının bir diğer kullanım yeri * ise "std::variant" sınıfını "nullable" hale getirmektir. */ std::variant vx; if (vx.index() == 0) { std::cout << "Mono State\n"; } if (!vx.index()) { std::cout << "Mono State\n"; } if (std::holds_alternative(vx)) { std::cout << "Mono State\n"; } if (std::get_if(&vx)) { std::cout << "Mono State\n"; } vx = "Ulya Yuruk"; std::cout << std::get<3>(vx) << "\n"; vx = 1.2; std::cout << std::get<2>(vx) << "\n"; vx = 12; std::cout << std::get<1>(vx) << "\n"; vx = std::monostate{}; // vx = {}; // vx.emplace(); // vx.emplace<0>(); if (vx.index() == 0) { std::cout << "Mono State\n"; } } } * Örnek 11, #include #include #include #include struct PrintVisitor { void operator()(int x)const { std::cout << "int : " << x << "\n"; } void operator()(double x)const { std::cout << "double: " << x << "\n"; } void operator()(const std::string& x)const { std::cout << "string: " << x << "\n"; } }; struct PrintVisitorTemp { // Alternative Way - I template void operator()(T x)const { std::cout << "[" << x << "]\n"; } template<> void operator()(const std::string& x) const { std::cout << "[" << x << "]\n"; } // Alternative Way - II, Since C++20 /* void operator()(const auto& x)const{ std::cout << "[" << x << "]\n"; } */ // Alternative Way - III /* template void operator()(const T& x) { if constexpr (std::is_same_v) { std::cout << "int, [" << x << "]\n"; } else if constexpr (std::is_same_v) { std::cout << "double, [" << x << "]\n"; } else if constexpr (std::is_same_v) { std::cout << "string, [" << x << "]\n"; } } */ }; struct IncVisitor { template void operator()(T& x) { ++x; } template<> void operator()(std::string& x) { x += x; } }; class Myclass {}; struct MyCustomCallable { void operator()(char)const { std::cout << "char\n"; } void operator()(int)const { std::cout << "int\n"; } void operator()(double)const { std::cout << "double\n"; } void operator()(Myclass)const { std::cout << "Myclass\n"; } void operator()(auto)const { std::cout << "Others\n"; } }; int main() { { /* * 1-) Şimdi de "std::visit" fonksiyonunu inceleyelim. Bu fonksiyon * bir "callable" alıyor ve her bir alternatif için bu "callable" ı * çağırıyor. Dolayısıyla ilgili "callable" nesnesi içerisinde, * alternatiflerin türleri parametre olarak alan fonksiyonlar olması * gerekmektedir. Aksi halde sentaks hatası alacağız. */ std::variant vx("Ulya Yuruk"); std::visit(PrintVisitor{}, vx); vx = 1.2; PrintVisitor pv; std::visit(pv, vx); vx = 12; std::visit(PrintVisitor{}, vx); } puts("#########################"); { /* * 2-) Eğer bu fonksiyonların yaptıkları şey de aynı ise ilgili * fonksiyonu şablon olarak da yazabiliriz. */ std::variant vx("Ulya Yuruk"); std::visit(PrintVisitorTemp{}, vx); vx = 1.2; PrintVisitorTemp pv; std::visit(pv, vx); vx = 12; std::visit(PrintVisitorTemp{}, vx); } puts("#########################"); { /* * 3-) Tabii bu fonksiyona her defasında farklı bir işi yapan * "callable" da geçilebilir. */ std::variant vx("Ulya Yuruk"); std::visit(PrintVisitorTemp{}, vx); std::visit(IncVisitor{}, vx); std::visit(PrintVisitorTemp{}, vx); vx = 1.2; PrintVisitor pv; IncVisitor ic; std::visit(pv, vx); std::visit(ic, vx); std::visit(pv, vx); vx = 12; std::visit(PrintVisitorTemp{}, vx); std::visit(IncVisitor{}, vx); std::visit(PrintVisitorTemp{}, vx); } puts("#########################"); { /* * 4-) Bize bir "callable" lazım olduğundan, "lambda" ifadelerini * de kullanabiliriz. Fakat burada bizim "f" ismini kullanmamıza * gerek yoktur. İlgili "lambda" ifadesini fonksiyona direkt olarak * da gönderebiliriz. */ auto f = [](const auto& x) { std::cout << "(" << x << ")\n"; }; std::variant vx("Ulya Yuruk"); std::visit(f, vx); vx = 1.2; std::visit(f, vx); vx = 12; std::visit(f, vx); } puts("#########################"); { /* * 5-) Pek tabii iş bu "callable" sınıfını istediğimiz şekilde organize * edebiliriz. */ std::variant> vx; std::visit(MyCustomCallable{}, vx); vx = 12; std::visit(MyCustomCallable{}, vx); vx = 1.2; std::visit(MyCustomCallable{}, vx); vx = Myclass{}; std::visit(MyCustomCallable{}, vx); vx = std::bitset<16>(56u); std::visit(MyCustomCallable{}, vx); } } * Örnek 12, #include #include #include #include struct MyVisitor{ /* // Alternative - I template void operator()(const T& t, const U& u){ std::cout << typeid(T).name() << ", " << typeid(U).name() << " => "; std::cout << "(" << t << ", " << u << ")\n"; } */ /* // Alternative - II (C++20) void operator()(const auto& t, const auto& u){ std::cout << typeid(t).name() << ", " << typeid(u).name() << " => "; std::cout << "(" << t << ", " << u << ")\n"; } */ // Alternative - III void operator()(int t, int u){ std::cout << typeid(t).name() << ", " << typeid(u).name() << " => "; std::cout << "(" << t << ", " << u << ")\n"; } void operator()(const auto& t, const auto& u){ std::cout << "Other types!..\n"; } }; int main(void) { { std::variant vx{3.4}; std::variant vy{34}; std::visit(MyVisitor{}, vx, vy); vx = "Ulya Yuruk"; vy = 4.3f; std::visit(MyVisitor{}, vx, vy); } { auto fn = [](const auto& t, const auto& u){ std::cout << typeid(t).name() << ", " << typeid(u).name() << " => "; std::cout << "(" << t << ", " << u << ")\n"; }; std::variant vx{3.4}; std::variant vy{34}; std::visit(fn, vx, vy); vx = "Ulya Yuruk"; vy = 4.3f; std::visit(fn, vx, vy); } return 0; } * Örnek 13, #include #include #include #include class Nec : public std::variant{ //... }; int main(void) { Nec nec{ "Ulya Yuruk" }; std::cout << "Index : " << nec.index() << ", "; std::cout << std::get<1>(nec) << "\n"; nec.emplace<0>(31); std::cout << "Index : " << nec.index() << ", "; std::cout << std::get<0>(nec) << "\n"; return 0; } * Örnek 14, Aşağıdaki örnekte ise ilgili "variant" nesnesinin alternatif eklenirken bir hata gönderilmiştir. Dolayısıyla ilgili alternatif oluşturulamamıştır. İşte bu durumu tespit eden ise ".valueless_by_exception()" isimli fonksiyondur. Bu durumda artık ".index()" fonksiyonu ise artık "std::variant_npos" konumunu döndürecektir. #include #include #include struct S { operator int() const{ /* * 2-) Sonrasında bu fonksiyon çağrılacaktır fakat programın akışı * "return" deyimine girmeyecektir. Dolayısıyla ilgili alternatif * OLUŞTURULMAMIŞ OLACAKTIR. */ throw std::runtime_error{ "hata"}; return 1; } }; int main() { using namespace std; variant var{ 12.2 }; try { /* * 1-) "int" yerine yeni bir alternatif eklenmek istenmiştir. */ var.emplace<1>(S{}); } catch (const exception& ex) { /* * 3-) Bu durumda "std::variant" nesnesi geçersiz durumda olacaktır. */ cout << "hata yakalandi: " << ex.what() << "\n"; cout << boolalpha << var.valueless_by_exception() << "\n"; cout << "Index: " << var.index() << "\n"; cout << (var.index() == variant_npos) << "\n"; } } * Örnek 15, #include #include #include using v_type = std::variant; int main() { { constexpr auto n{ std::variant_size::value }; // constexpr auto n{ std::variant_size_v }; std::cout << "Total of <" << n << "> different types in the variant object\n"; } { std::variant_alternative<0, v_type>::type i; // int i; // std::variant_alternative_t<0, v_type> i; // int i; std::variant_alternative<1, v_type>::type d; // double d; // std::variant_alternative_t<1, v_type> d; // double d; std::variant_alternative<2, v_type>::type l; // long l; std::variant_alternative<3, v_type>::type c; // char c; std::variant_alternative<4, v_type>::type s; // std::string s; } } * Örnek 16, #include #include struct Data { //... }; enum ErrorType { System, Archieve, Log }; std::variant foo() { /* * Bir hata durumunda ilgili "std::variant" * nesnesi "ErrorType" türünü tutarken, hata * olmadığında ise "Data" türünü tutacaktır. */ //... } int main() { //... } * Örnek 17, İşin başında toplamda kaç adet türemiş sınıf olacağı belliyse ve daha sonra ekleme yapılmayacağı kesinse, "std::variant" sınıfını kalıtıma alternatif olarak da kullanabiliriz. Böylesi durumlara ise "Closed Inheritence" denir. #include #include class Xls {}; class Pdf {}; class Txt {}; class Word {}; using Document = std::variant; int main() { //... } > Hatırlatıcı Notlar: >> "Value Type" : Her nesnenin değeri kendine demektir. Yani ortada paylaşılan bir alan/değer söz konusu değildir. Yani kopyalama yapıldığında "Deep Copy" yapılmakta, "Shallow Copy" yapılmamaktadır.