top of page

C++ Dilinde std::variant

  • Writer: Yusuf Hançar
    Yusuf Hançar
  • Sep 3, 2024
  • 16 min read

Önceki yazımızda std::optional sınıf şablonunu incelemiştik. Bu yazımızda C++17 ile standartlara eklenen std::variant, çoklu türleri bir arada tutmak için kullanılan variadic bir sınıf şablonudur. std::variant, bir değişkenin yalnızca önceden belirlenmiş bir tür kümesinden birine sahip olmasını sağlar. std::variant tür olarak referansları, dizileri veya void türünü tutamaz. Bu, C dilinde kullanılan union yapısına benzer, ancak daha güvenli ve kullanımı daha rahat bir şekilde tasarlanmıştır.


std::variant temel özelliklerini birkaç madde halinde ele alalım.

  1. Tür Seçenekleri:

    1. std::variant belirli türlerden birini saklar. Her ne kadar bir std::variant birden fazla türden değer saklayabilse de, aynı anda yalnızca bir türü tutar. Saklanan türler variant alternatives olarak adlandırılır.

      1. https://en.cppreference.com/w/cpp/utility/variant/valueless_by_exception

  2. Value Semantics:

    1. std::variant, değer semantiği sağlar. Bu, bir std::variant nesnesinin başka bir std::variant nesnesine atandığında, her iki nesnenin birbirinden tamamen bağımsız olduğunu ve birinin değiştirilmesinin diğerini etkilemediğini ifade eder. Kopyalama sırasında deep copy yapılır; dolayısıyla bir türün kopyası diğerine bağlı değildir.

  3. Depolama:

    1. std::variant, sakladığı türlerin en büyük boyutuna göre bir size değerine sahiptir. Bu, türlerden birinin diğerlerinden çok daha büyük olabileceği durumlarda bile uyumluluğu sağlar.

  4. Optional ve Nullable:

    1. std::variant, doğrudan nullable bir tür değildir. Ancak std::monostate türünü kullanarak bir std::variant nesnesinin "boş" olduğunu ifade edebilirsiniz. std::monostate, std::variant'ın "hiçbir türü" temsil eden bir alternatifi olarak kullanılır.

  5. Kullanım Senaryoları:

    1. std::variant; hata kontrolü, parse işlemleri, kalıtıma alternatif durumları, ve state machines gibi birçok durumda faydalı olabilir. Özellikle türler arasında seçim yaparken veya farklı türleri tek bir değişkende saklamanız gerektiğinde kullanışlıdır.

  6. C Union ve std::variant Farkı:

    1. C dilindeki union, hangi alternatifin geçerli olduğunu bilmez ve güvenli olmayan bir şekilde veri paylaşımı yapabilir.

    2. std::variant ise hangi türün geçerli olduğunu bilmek için std::get ve std::visit gibi fonksiyonlar sağlar ve tür güvenliği sunar.


#include <stdio.h>

union SmartCode {
    int idx;
    float flt;
    char str[20];
};

int main() 
{
    union SmartCode sc;

    // Integer degeri ayarla
    sc.idx = 10;
    printf("sc.idx: %d\n", sc.idx);

    // Float degeri ayarlandi (integer degeri gecersiz kilar)
    sc.flt = 3.14;
    printf("sc.flt: %f\n", sc.flt);

    // String degeri ayarla (float degeri gecersiz kilar)
    snprintf(sc.str, sizeof(sc.str), "Cpp17");
    printf("sc.str: %s\n", sc.str);

    // Integer degeri tekrar yazildi
    printf("sc.idx: %d\n", sc.idx); // Bu sonuc tahmin edilemez ve gecersiz olabilir

    return 0;
}
*****************************     
AÇIKLAMA : union güvenli olmayan bir şekilde veri paylaşımı yapabilir. 
*****************************

cppreference std::variat örneği ile örneklerimize başlayalım
#include <cassert>
#include <iostream>
#include <string>
#include <variant>
 
int main()
{
    std::variant<int, float> v, w;
    v = 42; // v contains int
    int i = std::get<int>(v);
    assert(42 == i); // succeeds
    w = std::get<int>(v);
    w = std::get<0>(v); // same effect as the previous line
    w = v; // same effect as the previous line
 
    //  std::get<double>(v); // error: no double in [int, float]
    //  std::get<3>(v);      // error: valid index values are 0 and 1
 
    try
    {
        std::get<float>(w); // w contains int, not float: will throw
    }
    catch (const std::bad_variant_access& ex)
    {
        std::cout << ex.what() << '\n';
    }
 
    using namespace std::literals;
 
    std::variant<std::string> x("abc");
    // converting constructors work when unambiguous
    x = "def"; // converting assignment also works when unambiguous
 
    std::variant<std::string, void const*> y("abc");
    // casts to void const* when passed a char const*
    assert(std::holds_alternative<void const*>(y)); // succeeds
    y = "xyz"s;
    assert(std::holds_alternative<std::string>(y)); // succeeds
}
*****************************     
CEVAP : std::get: wrong index for variant
*****************************

#include <variant>

int main() 
{
    std::variant<int, double, std::string> var;
}
*****************************     
AÇIKLAMA : default construct edildiği icin ilk alternatifi tutar.
*****************************

#include <variant>

class Data {
public :
    Data(int) {}
};

int main() 
{
    std::variant<Data, double, long> var;
}
*****************************     
AÇIKLAMA : variant def construct edildi ve ilk argumanı gösterdi ancak ilk argumanın def ctor'ı not declared.
*****************************
CEVAP : syntax error
*****************************

#include <variant>

int main() 
{
    std::variant<int, double, long> var{ 12 };
}
*****************************     
AÇIKLAMA : geçilen parametre(12) int olduğu için int alternative tutar.
*****************************
ambiguity durumu söz konusu olabilir!
#include <variant>

int main() 
{
    std::variant<int, float, char> var{ 12.5 }; 
}
*****************************
CEVAP : ambiguity
*****************************

#include <variant>

int main() 
{
    std::variant<int, double, char> var{ 12.5f }; 
}
*****************************     
AÇIKLAMA : float argument gönderdik "float to double" promotion olduğu için double seçilir.
*****************************
CEVAP :  ambiguity yok
*****************************

#include <iostream>
#include <variant>
#include <string>
#include <string_view>

int main() 
{
    std::variant<int, std::string, std::string_view> var{ "smartcode" };
}
*****************************
CEVAP : ambiguity 
*****************************

std::variant şablonunun tuttuğu nesneyi sınamanın iki yolu vardır:

  1. sınıfın const üye fonksiyonu olan "index()"

#include <variant>

int main() 
{
    std::variant<int, double, char> var;
    
    std::cout << var.index();
}
*****************************
CEVAP : 0
*****************************

#include <variant>

int main() 
{
    std::variant<int, double, float, char> var{ 4.5 };
    
    std::cout << var.index();
}
*****************************
CEVAP : 1     
*****************************

2. ikinci seçenek boolean değer döndüren "holds_alternative"

#include <iostream>
#include <variant>
#include <string_view> 

int main() 
{
    std::variant<int, double, float, char> var{ 4.5 };

    std::cout << std::boolalpha;  

    std::cout << std::holds_alternative<int>(var) << "\n";
    std::cout << std::holds_alternative<double>(var) << "\n";
    std::cout << std::holds_alternative<float>(var) << "\n";
    std::cout << std::holds_alternative<char>(var);

    return 0;
}
*****************************
CEVAP : 
false
true
false
false
*****************************

std::variant nesnesini oluştururken std::optional ın std::in_place "disambiguation tags" yardımcısı gibi std::variant şablonunun constexpr :
  • std::in_place_index

  • std::in_place_type

    • disambiguation tag


#include <iostream>
#include <variant>
#include <string> 

int main() 
{
    std::variant<int, std::string, double> var{ 10, 'a' };
}
*****************************     
AÇIKLAMA : bu şekilde bir string oluştursun diyemeyiz.
*****************************
CEVAP : syntax error    
*****************************

int main() 
{
    std::variant<int, std::string, double> var{ std::in_place_index<1>, 10, 'a' };
}
*****************************     
AÇIKLAMA : bu şekilde 1 indexindeki string türden bir değişken oluşturabiliriz.
*****************************


std::optional için verdiğimiz Date sınıfını buraya uyarlayalım.
#include <iostream>
#include <string>
#include <vector>
#include <variant>

std::vector<std::string> all_month = {"Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"};

std::vector<std::string> all_day = {"Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi"};

class Date {
public:
    Date(int day, int month, int year) : m_day(day), m_month(month), m_year(year) {}

    std::string convert_to_date() const 
    {
        std::string dname = all_day[get_day_of_week()];
        std::string mname = all_month[m_month - 1]; 
        
        return std::to_string(m_day) + " " + mname + " " + 
               std::to_string(m_year) + " " + dname;
    }

    int get_day_of_week() const 
    {
        int day = (14 - m_month) / 12;
        int year = m_year - day;
        int mnt = m_month + 12 * day - 2;
        
        return (m_day + year + year / 4 - year / 100 + year / 400 + (31 * mnt)                                    / 12) % 7;
    }

private:
    int m_day;
    int m_month;
    int m_year;
};

int main() 
{
    std::variant<int, std::string, double, Date> var{ Date{29, 5, 1994} };
    
    if (std::holds_alternative<Date>(var)) 
    {
        std::cout << std::get<Date>(var).convert_to_date() << "\n";
    } 
    else 
    {
        std::cout << "variant does not hold a Date\n";
    }

    return 0;
}
*****************************
CEVAP :  18 Ağustos 2024 Pazar  
*****************************

std::holds_alternative fonksyionu
  • std::variant nesnesinin belirli bir türde bir değeri içerip içermediğini kontrol etmek için kullanılan bir helper fonksiyondur.

int main() 
{
    std::variant<int, std::string, double, Date>var{std::in_place_type<Date>, 18,8,2024 };
    
    if (std::holds_alternative<Date>(var)) 
    {
        std::cout << std::get<Date>(var).convert_to_date() << "\n";
    } 
    else 
    {
        std::cout << "Variant does not hold a Date\n";
    }

    return 0;
}
*****************************
CEVAP : 18 Ağustos 2024 Pazar
*****************************

Bu kontrol derleme zamanında yapılır.
#include <iostream>
#include <variant>

int main() 
{
    std::variant<int, double, long> var;

    auto val = std::holds_alternative<char>(var);
}
*****************************     
AÇIKLAMA : bu kontrollerin compile time da yapıldığını göstermek için yazdık.
*****************************
CEVAP : compile time error   
*****************************


#include <iostream>
#include <variant>

class Data {
public :
    Data() { std::cout << "def ctor" << "\n"; }

    Data(const Data&) { std::cout << "copy ctor" << "\n"; }
};

int main() 
{
    std::variant<int, Data, double> va{ Data{} };
}
*****************************     
AÇIKLAMA : Data{} ifadesi, Data türünde bir nesne oluşturur. Bu işlem Data sınıfının default constructor'ını çağırır. Daha sonra da std::variant, içindeki türlerden birine atama yaparken copy constructor'ını  çağırır. 
*****************************
CEVAP :    
def ctor
copy ctor
*****************************

class Data {
public :
    Data() { std::cout << "def ctor" << "\n"; }

    Data(const Data&) { std::cout << "copy ctor" << "\n"; }
};

int main() 
{
    std::variant<int, Data, double> va1{ std::in_place_index<1> };
    std::variant<int, Data, double> va2{ std::in_place_type<Data> };    
}
*****************************     
AÇIKLAMA : Data türünden nesne oluşturma ile ilgili
*****************************
CEVAP :    
def ctor
def ctor
*****************************

ayrıca std::in_place_index ve std::in_place_type ambiguity durumunu önler.
#include <iostream>
#include <variant>

int main() 
{
    std::variant<int, float, char> var{ 5u };
} 
*****************************
CEVAP : ambiguity
*****************************

#include <iostream>
#include <variant>

int main() 
{
    std::variant<int, float, char> var{ std::in_place_type<float>, 5u };
}
*****************************
CEVAP :    legal
*****************************

int main() 
{
    std::variant<int, double, int> var(5);  
}
*****************************
CEVAP :    ambiguity
*****************************

int main() 
{
    std::variant<int, double, int> var(std::in_place_index<0>, 5);  
}
*****************************
CEVAP :    legal
*****************************

#include <iostream>
#include <variant>

struct Smart {
    int x, y;
};

struct Code {
    double x, y;
};

struct Buf {
    unsigned char buf[256];
};

using variant_t = std::variant<int, double, long>;

int main() 
{
    std::cout << "sizeof(Smart)     : " << sizeof(Smart) << "\n";
    std::cout << "sizeof(Code)      : " << sizeof(Code) << "\n";
    std::cout << "sizeof(Buf)       : " << sizeof(Buf) << "\n";
    std::cout << "sizeof(variant_t) : " << sizeof(variant_t);
}
*****************************
CEVAP :    
sizeof(Smart)     : 8
sizeof(Code)      : 16
sizeof(Buf)       : 256
sizeof(variant_t) : 16
*****************************

std::variant sınıf şablonunun tuttuğu nesneye erişim için :
  1. "std::get" fonksiyon şablonu


#include <iostream>
#include <variant>

int main() 
{
    std::variant<int, double, long> var(10);

    std::cout << std::get<0>(var) << "\n";
}
*****************************
CEVAP : 10
*****************************

int main() 
{
    std::variant<int, double, long> var(10.5);

    try 
    {
        std::cout << std::get<0>(var) << "\n";
    }
    catch (const std::exception& ex)
    {
        std::cout << "exception caught : " << ex.what() << "\n";
    }
}
*****************************
CEVAP : exception caught : std::get: wrong index for variant
*****************************

int main() 
{
    std::variant<int, double, long> var(10);

    std::get<0>(var) = 45;

    std::cout << std::get<0>(var) << "\n";
}
*****************************
CEVAP : 45
*****************************

int main() 
{
    std::variant<int, double, std::string> var("smartcode");

    std::cout << std::get<std::string>(var) << "\n";
}
*****************************
CEVAP : smartcode
*****************************

2. std::get_if sınıf şablonu

  • exception throw etmez ve pointer semantiği ile kullanılır.

  • geri dönüş değeri template argument türden pointer olur.

#include <iostream>
#include <variant>

int main() 
{
    std::variant<int, double, std::string> var("smartcode");

    std::get_if<2>(&var);
    std::get_if<std::string>(&var);
}

#include <iostream>
#include <variant>

int main() 
{
    std::variant<int, double, std::string> var("smartcode");

    if (var.index() == 2)
    {
        std::cout << "alternative string is " << std::get<2>(var) << "\n";
    }
} 
*****************************
CEVAP : alternative string is smartcode
*****************************

#include <iostream>
#include <variant>

int main() 
{
    std::variant<int, double, std::string> var("smartcode");

    if (std::holds_alternative<std::string>(var))
    {
        std::cout << "alternative string is " << std::get<2>(var) << "\n";
    }
}
*****************************
CEVAP : alternative string is smartcode
*****************************

#include <iostream>
#include <variant>

int main() 
{
    std::variant<int, double, std::string> var("smartcode");

    if (auto vptr = std::get_if<std::string>(&var))
    {
        std::cout << "alternative string is " << *vptr << "\n";
    }
}
*****************************
CEVAP : alternative string is smartcode
*****************************

#include <iostream>
#include <variant>

int main() 
{
    std::variant<int, double, std::string> var(10.5);

    if (std::holds_alternative<int>(var))
    {
        std::cout << "int" << "\n";
    }
    else if (std::holds_alternative<double>(var))
    {
        std::cout << "double" << "\n";
    }
    else if (std::holds_alternative<std::string>(var))
    {
        std::cout << "string" << "\n";
    }
}
*****************************
CEVAP : double
*****************************

int main() 
{
    std::variant<bool, std::string> var("smartcode");

    std::cout << "var.index() : " << var.index() << "\n";
} 
*****************************
CEVAP : var.index() : 1
*****************************

#include <iostream>
#include <variant>

int main() 
{
    enum idx : size_t { age, wage, name };

    using Age = int;
    using Wage = double;
    using Name = std::string;

    std::variant<Age, Wage, Name> var(29);

    std::cout << std::get<age>(var) << " - ";
    std::cout << std::get<Age>(var) << " - ";

    var = "smartcode";

    std::cout << std::get<name>(var) << " - ";
    std::cout << std::get<Name>(var);
}
*****************************
CEVAP : 29 - 29 - smartcode - smartcode
*****************************

std::variant nesnesine atama yapmanın yolları :

  • doğrudan atama operatoru ile :

int main() 
{
    std::variant<int, double, std::string> var;

    var = 12;
    var = "smartcode";
}

int main() 
{
    std::variant<int, double, std::string> var;

    var = (10, 'a');
}

int main() 
{
    std::variant<int, double, std::string> var;

    var = std::string(10, 'a');
}

emplace fonksiyon şablonu ile :
  • emplace eski değeri de destroy eder.

int main() 
{
    std::variant<int, double, std::string> var;

    var.emplace<double>(12.5);
}

int main() 
{
    std::variant<int, double, std::string> var;

    var.emplace<std::string>(10, 'a');
}

std::monostate
  • struct monostate { }; (since C++17)

  • bir pattern olarak ele alınır.

  • tek bir state'e sahip anlamındadır. Tüm nesneler aynı state içersindedir.

    • Öyle bir sınıf olsun ki; hiçbir non-static veri elemanı olmasın, tüm elemanları static olsun. Bu durumda sınıfın nesneleri arasında bir farklılık olmayacaktır. Static üye fonksiyonlar da bütün nesneler için aynı şekilde çağırılır. Böyle sınıflara monostate sınıf denir.


class Monostate {
public:
    int get() const 
    {
        return m_value;
    }

    void set(int value) 
    {
        m_value = value;
    }

private:
    static int m_value; // Tüm örnekler aynı veriyi paylaşır.
};

int Monostate::m_value = 0;

int main() 
{
    Monostate obj1;
    Monostate obj2;

    obj1.set(42);

    // obj2 değişkeni de aynı değeri paylaşır.
    std::cout << "obj1 value: " << obj1.get() << std::endl; // 42
    std::cout << "obj2 value: " << obj2.set() << std::endl; // 42

    return 0;
}

  • cppreference örnek

#include <cassert>
#include <iostream>
#include <variant>
 
struct S
{
    S(int i) : i(i) {}
    int i;
};
 
int main()
{
    // Without the monostate type this declaration will fail.
    // This is because S is not default-constructible.
    std::variant<std::monostate, S> var;
    assert(var.index() == 0);
 
    try
    {
        std::get<S>(var); // throws! We need to assign a value
    }
    catch(const std::bad_variant_access& e)
    {
        std::cout << e.what() << '\n';
    }
 
    var = 42;
    std::cout << "std::get: " << std::get<S>(var).i << '\n'
              << "std::hash: " << std::hex << std::showbase
              << std::hash<std::monostate>{}(std::monostate{}) << '\n';
}

std::monostate 2 nedenden dolayı std::variant alternatifi olarak kullanılabilir.
  • std::monostate tek bir state'e sahip olduğundan

    • karşılaştırma işlemlerinde vs kullanılabilir.

  • default constructable bir std::variant oluşturmada kullanılır.


struct A { A(int); };
struct B { B(double); };

int main() 
{
    std::variant<A, B> var;
}
*****************************     
AÇIKLAMA : bu şekilde def construct etmek istesek mümkün olmayacaktır.
Çünkü ilk alternative için def ctor çağırılmalıdır.
*****************************
CEVAP :error: use of deleted function‘std::variant<_Types>::variant()[with _Types={A, B}]’
*****************************

struct A { A(int); };
struct B { B(double); };

int main() 
{
    std::variant<std::monostate, A, B> var;
}
*****************************     
AÇIKLAMA : std::monostate sınıfı, std::variant içinde bir "boş" veya "anlamlı veri olmayan" durumun temsilcisi olarak işlev görür.
*****************************
CEVAP : legal
*****************************

int main() 
{
    std::variant<std::monostate, int, double, std::string> var;

    if (var.index() == 0)
    {
        std::cout << "monostate" << "\n";
    }
}
*****************************
CEVAP : monostate
*****************************

int main() 
{
    std::variant<std::monostate, int, double, std::string> var;

    if (!var.index())
    {
        std::cout << "monostate" << "\n";
    }
}
*****************************
CEVAP : monostate
*****************************

int main() 
{
    std::variant<std::monostate, int, double, std::string> var;

    if (std::holds_alternative<std::monostate>(var))
    {
        std::cout << "monostate" << "\n";
    }
}
*****************************
CEVAP : monostate
*****************************

int main() 
{
    std::variant<std::monostate, int, double, std::string> var;

    if (std::get_if<std::monostate>(&var))
    {
        std::cout << "monostate" << "\n";
    }
}
*****************************
CEVAP : monostate
*****************************

int main() 
{
    std::variant<std::monostate, int, double, std::string> var{ 4.5 };

    var = std::monostate{};

    if (std::get_if<std::monostate>(&var))
    {
        std::cout << "monostate" << "\n";
    }
}
*****************************
CEVAP : monostate
*****************************

int main() 
{
    std::variant<std::monostate, int, double, std::string> var{ 4.5 };

    var = {};

    if (std::get_if<std::monostate>(&var))
    {
        std::cout << "monostate" << "\n";
    }
}
*****************************
CEVAP : monostate
*****************************

int main() 
{
    std::variant<std::monostate, int, double, std::string> var{ 4.5 };

    var.emplace<std::monostate>();

    if (std::get_if<std::monostate>(&var))
    {
        std::cout << "monostate" << "\n";
    }
}
*****************************
CEVAP : monostate
*****************************

int main() 
{
    std::variant<std::monostate, int, double, std::string> var{ 4.5 };

    var.emplace<0>();

    if (std::get_if<std::monostate>(&var))
    {
        std::cout << "monostate" << "\n";
    }
}
*****************************
CEVAP : monostate
*****************************

  • Visitor Pattern genellikle farklı türdeki nesneler üzerinde aynı işlemi gerçekleştirmek için kullanılır. Bu pattern; nesnelerin türlerini ayrı ayrı ele almak yerine, onları dolaşan bir Visitor aracılığıyla işlem yapmayı sağlar. Bu sayede, yeni türler eklemek veya mevcut türleri değiştirmek gibi değişiklikler yaparken kodun diğer bölümlerini etkilemeden işlem yapabilirsiniz.

    std::variant; birden fazla türü kapsayan bir veri tipi oluşturmanıza olanak tanırken, std::visit ise bu türler arasındaki yönlendirmeyi sağlar.

    Bir variant nesnesi üzerinde işlem yaparken, her bir alternatif tür için bir işlev sağlamak ve bu işlevi std::visit ile çağırmak yaygın bir yaklaşımdır.


// class... birden fazla template parametresi olabilir demek
template <class Visitor, class... Variants>                
constexpr visit(Visitor&& vis, Variants&&... vars);
*****************************     
AÇIKLAMA : 
fonksiyonun her iki parametresi de universal reference. Yani gönderilen callable ve variant nesneleri, L value ya da R value olabilir.

Burada visit fonksiyonu; callable'ı kullanarak(Visitor&& vis) visit fonksiyonuna argument olarak gönderilen variant'ın alternatifini bu callable'a arguman olarak gönderecektir.
Artık alternatifin ne olduğunun seçimini visit fonksiyonu kendisi yapar.
*****************************

  • Oluşturulan std::variant nesnelerini çıkışa akımına verip yazdırmak isteyelim.

  • Bunun için bir callable oluşturulacak ve bu callable visit fonkisyonuna arguman olarak geçilecektir.

int main() 
{
    std::variant<int, double, std::string> var{ "smartcode" };
}

struct PrintVisitor {};

int main() 
{
    std::variant<int, double, std::string> var{ "smartcode" };

    Printvisitor pr;

    std::visit<pr, var);
    //ya da 
    visit<PrintVisitor{}, var);
}

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";
    }
};

int main() 
{
    std::variant<int, double, std::string> var{ "smartcode" };

    std::visit(PrintVisitor{}, var);
}
*****************************
CEVAP : string  : smartcode
*****************************

  • member template olarak da yazabiliriz.


struct PrintVisitor {
    template <typename T>
    void operator()(const T& x) const
    {
        std::cout << "int " << x << "\n";
    }
};

int main() 
{
    std::variant<int, double, std::string> var{ "smartcode" };

    std::visit(PrintVisitor{}, var);
}

  • EK NOT:

struct PrintVisitor {
    void operator()(const auto& x) const
    {
        std::cout << "int " << x << "\n";
    }
};
*****************************     
AÇIKLAMA :  c++20 ile const auto& x şeklinde yazabiliriz. Bu yine bir member template olur.
*****************************

struct PrintVisitor {
    template <typename T>
    void operator()(const T& x) const
    {
        if constexpr(std::is_same_v<T, int>)
        {
            std::cout << "int : " << x << "\n";
        }
        else if constexpr(std::is_same_v<T, double>)
        {
            std::cout << "double : " << x << "\n";
        }
        else if constexpr(std::is_same_v<T, std::string>)
        {
            std::cout << "string : " << x << "\n";
        }
    }
};

int main() 
{
    std::variant<int, double, std::string> var{ "smartcode" };

    std::visit(PrintVisitor{}, var);
}

struct PrintVisitor {
    template <typename T>
    void operator()(const T& x) const
    {
        if constexpr(std::is_same_v<T, int>)
        {
            std::cout << "int : " << x << "\n";
        }
        else if constexpr(std::is_same_v<T, double>)
        {
            std::cout << "double : " << x << "\n";
        }
        else if constexpr(std::is_same_v<T, std::string>)
        {
            std::cout << "string : " << x << "\n";
        }
    }
};

struct MathVisitor {
    void operator()(int& r)
    {
        ++r;
    }

    void operator()(double& r)
    {
        r *= r;
    }

    void operator()(std::string& r)
    {
        r += r;

        std::cout << "string : " << r << "\n";
    }
};

int main() 
{
    std::variant<int, double, std::string> var{ "smartcode" };

    std::visit(PrintVisitor{}, var);
    std::visit(MathVisitor{}, var);
}

int main() 
{
    std::variant<int, double, std::string> var{ "smartcode" };
    
    auto pr = [](const auto& val) {
        std::cout << "val : " << val << "\n";
    };

    visit(pr, var);
}

int main() 
{
    std::variant<int, double, std::string> var{ "smartcode" };
    
    visit([](const auto& x) {
        std::cout << "x : " << x << "\n";
    }, var);
}
*****************************     
AÇIKLAMA :  lambda ifadesi doğrudan bir geçici nesnedir zaten bu şekilde de yazabiliriz.
*****************************

#include <iostream>
#include <variant>

struct Vst {
    template <typename T, typename U>
    void operator()(const T& t, const U& u)
    {
        std::cout << typeid(T).name() << ", " << typeid(U).name() << "\n";
        std::cout << t << ", " << u << "\n";
    }
};

int main() 
{
    std::variant<int, double, std::string> va1{ 3.4 };
    std::variant<long, int, std::string> va2{ 56 };

    std::visit(Vst{}, va1, va2);
}
*****************************
CEVAP : 
double, int
3.4, 56
*****************************

int main() 
{
    std::variant<int, double, std::string> va1{ 3.4 };
    std::variant<long, int, std::string> va2{ 56 };

    auto fn = [](const auto& t, const auto& u) {
        std::cout << typeid(t).name() << ", " << typeid(u).name() << "\n";
        std::cout << t << ", " << u << "\n";
    };

    std::visit(fn, va1, va2); 
}

  • union üzerinden kalıtım yapılmaz ancak std::variant üzerinden kalıtım yapılır.

  • std::variant kalıtımda Base class olarak kullanılabilir.


#include <iostream>
#include <variant>

class Derived : public std::variant<int, std::string>{};

int main() 
{
    Derived d = { "smartcode" };
    std::cout << d.index() << "\n";
    std::cout << std::get<1>(d) << "\n";
    d.emplace<0>(20);
    std::cout << std::get<0>(d) << "\n";
}
*****************************     
AÇIKLAMA :  
*****************************
CEVAP : 
1
smartcode
20
*****************************

  • std::variant overload edilebilir.


template <typename ...Ts>
struct Overload : Ts...
{
    using Ts::operator()...;
};

template <typename ...Ts>
Overload(Ts...)->Overload<Ts...>;

int main ()
{
    std::variant<int, std::string> va{ 99 };

    std::visit(Overload {
        [](int val){ std::cout << "int : " << val << "\n"; }, 
        [](std::string val){ std::cout << "string : " << val << "\n"; },
        }, va);

    auto twice = Overload{ 
        [](std::string& str){ str += str; },
        [](auto& i){ i *= 2; },
    };

    std::visit(twice, va);
    std::cout << std::get<0>(va) << "\n";
}
*****************************
CEVAP : 
int : 99
198
*****************************

std::variant İçindeki Alternatiflerin Yönetimi ve valueless_by_exception Fonksiyonu

std::variant içindeki bir alternatif türün değiştirilmesi, örneğin emplace() fonksiyonu kullanılarak yapılabilir. Ancak, bu tür değişiklikleri yaparken dikkat edilmesi gereken önemli bir durum vardır: Eski alternatif yok edilirken ve yeni alternatif oluşturulurken bir istisna (exception) meydana gelebilir.


  • Alternatiflerin Değiştirilmesi ve İstisnalar

Bir std::variant içinde alternatif tür değiştirilirken, eski türün yok edilmesi ve yeni türün oluşturulması süreci iki aşamadan oluşur:

  1. Eski Alternatifin Yok Edilmesi: std::variant içindeki mevcut türün yok edilmesi (destruct) gerekir. Bu işlem, önceki alternatifin kaynaklarını serbest bırakır.

  2. Yeni Alternatifin Oluşturulması: Yeni türün konstrüksiyonu yapılır. Bu aşamada, eğer yeni türün yapıcısında bir hata veya istisna meydana gelirse, bu durum std::variant'ın geçersiz bir durumda kalmasına yol açabilir.


  • Geçersiz Durum ve valueless_by_exception Fonksiyonu

std::variant'ın geçersiz bir durumda olup olmadığını tespit etmek için valueless_by_exception() fonksiyonu kullanılabilir. Eğer valueless_by_exception() fonksiyonu true dönerse, bu, std::variant'ın geçersiz bir durumda olduğu ve mevcut türünün geçerli olmadığı anlamına gelir.


#include <iostream>
#include <variant>

struct Var {
    operator int() const    
    {
        throw std::runtime_error{ "error" };
    
        return 1;
    }
};

// return statement Var nesnesini int'e dönüştürecek explicit olmayan bir tür dönüştürme // operator int() fonksiyonu.

int main ()
{
     std::variant<double, int> va{ 12.5 };

    try 
    {
        va.emplace<1>(Var{});
    }
    catch (const std::exception& ex)
    {
        std::cout << "exception caught : " << ex.what() << "\n";
        std::cout << std::boolalpha << va.valueless_by_exception() << "\n";
        std::cout << "var.index() : " << va.index() << "\n";
        std::cout << (va.index() == std::variant_npos) << "\n";
    }   
}
*****************************     
AÇIKLAMA : 
*****************************
CEVAP : 
exception caught : error
false
var.index() : 0
false
*****************************

std::variant_size
using vtype = std::variant<int, double, long>;

int main ()
{
    constexpr auto n = std::variant_size_v<vtype>;  // 3
}

std::variant_alternative
  • compile time da tür hesaplamak için kullanılır

using vtype = std::variant<int, double, long>;

int main ()
{
    std::variant_alternative_t<1, vtype> va{};  // double
}

variant kullanım alanları
  • hata kodu döndürme senaryosu

  • run-time polymorphism veya kalıtıma bir alternatif oluşturması

    • run-time polymorphism(virtual dispatch) bir maliyeti vardır.


Örnek Senaryo : Bir taban sınıftan kalıtım yoluyla türemiş sınıflar elde ediyoruz, taban sınıfın sanal fonksiyonunu(larını) override ederiz ve virtual dispatch mekanizmasını kullanırız. Derleyici bu durumda bütün hiyerarşideki sınıf nesnelerinin içine vptr gömer. Her sınıf için bir sanal fonksiyon tablosu oluşturur. Bir taban sınıf veya referansı ile sanal fonksiyona çağrı yapıldığında virtual dispatch mekanizmasının devreye girmesi demek aslında sınıf nesnesinin içindeki v-pointer'ın get edilip ordan sanal fonksiyon tablosunda indexle bir fonksiyonun adresinin elde edilmesi ve o fonksiyonun çağırılması demektir. Hem run-time hem de bellek kullanımı açısından ilave maliyeti vardır.


  • Bellek kullanımı açısından : hiyerarşideki tüm nesnelerin içinde bir vptr olmak zorundadır.

  • Run-time başında bu sanal fonksiyon tablolarının(veri yapılarının) oluşturulması gerekir. Her polymorphic çağrı için bir get işlemi yapılıyor vs...

  • virtual dispatch ten yararlanabilmek için polymorphic nesnelerin dinamik ömürlü olmalıdır. Her polymorphic nesne için bir allocation yapılacak, delete edildiğinde de destruction süreci başlar. Bu da otomatik ömürlü nesneye göre işlem maliyeti demektir.

  • Bir diğer sakıncası da; sınıflar birbirine bağımlı hale gelmektedir.


class Document {
public :
    virtual void save() const = 0;
};

class Pdf : public Document {
public :
    void save() const override;
};

class Xls : public Document {
public :
    void save() const override;
};

class Word { public : };

class Pdf { public : };

class Xls { public : };

using Doc = std::variant<Word, Pdf, Xls>;
*****************************
AÇIKLAMA : std::variant ile alternatif kullanım
*****************************

struct Data {};

enum ErrorType { system, archive, log };

std::variant<Data, ErrorType> foo(){}

  • std::variant run-time polymorphism alternatifi olarak kullanılabilir.

#include <iostream>
#include <variant>
#include <list>
#include <functional>

class Cat {
public :
    Cat(std::string name) : m_name{ std::move(name) } {}

    void meow() const
    {
        std::cout << m_name << " miyavliyor..." << "\n";
    }

private :
    std::string m_name;
};

class Dog {
public :
    Dog(std::string name) : m_name{ std::move(name) } {}

    void woof() const
    {
        std::cout << m_name << " havliyor..." << "\n";
    }

private :
    std::string m_name;
};

class Lamb {
public :
    Lamb(std::string name) : m_name{ std::move(name) } {}

    void bleat() const
    {
        std::cout << m_name << " meeliyor..." << "\n";
    }

private :
    std::string m_name;
};

/************************************************************/

using Animal = std::variant<Dog, Cat, Lamb>;

template <typename T>
bool is_type(const Animal& a)
{
    return std::holds_alternative<T>(a);
}

// visitor olarak yazıldı, functor class olarak tanımlandı.
struct AnimalVoice {
    void operator()(const Dog& a) const { a.woof(); }
    void operator()(const Cat& a) const { a.meow(); }
    void operator()(const Lamb& a) const { a.bleat(); }
};

int main ()
{
    std::list<Animal> animal_farm{ Cat{ "pamuk" }, Dog{ "kont" }, Lamb{ "kuzucuk" }, Lamb{ "pamuk" }, Cat{ "kara" } };

    for (const Animal& a : animal_farm)
    {
        switch (a.index())
        {
            case 0 :
            {        
                std::get<Dog>(a).woof();
                break;
            }

            case 1 :
            {        
                std::get<Cat>(a).meow();
                break;
            }

            case 2 :
            {        
                std::get<Lamb>(a).bleat();
                break;
            }
        }
    }

    std::cout << "************************************" << "\n";

    for (const Animal& a : animal_farm)
    {
        if (const auto dog_ptr = std::get_if<Dog>(&a))
        {
            dog_ptr->woof();
        }
        else if (const auto cat_ptr = std::get_if<Cat>(&a))
        {
            cat_ptr->meow();
        }
        else if (const auto lamb_ptr = std::get_if<Lamb>(&a))
        {
            lamb_ptr->bleat();
        }
    }    

    std::cout << "************************************" << "\n";

    for (const Animal& a :animal_farm)
    {
        std::visit(AnimalVoice{}, a);
    }

    std::cout << "************************************" << "\n";

    std::cout << "hayvan ciftliginde " 
         << std::count_if(std::begin(animal_farm), std::end(animal_farm), is_type<Cat>)
         << "kedi, " 
         << std::count_if(std::begin(animal_farm), std::end(animal_farm), is_type<Dog>)
         << "kopek ve "
         << std::count_if(std::begin(animal_farm), std::end(animal_farm), is_type<Lamb>)
         << "kuzu yasiyor.";
}
*****************************
AÇIKLAMA : 
*****************************
CEVAP : 
pamuk miyavliyor...
kont havliyor...
kuzucuk meeliyor...
pamuk meeliyor...
kara miyavliyor...
************************************
pamuk miyavliyor...
kont havliyor...
kuzucuk meeliyor...
pamuk meeliyor...
kara miyavliyor...
************************************
pamuk miyavliyor...
kont havliyor...
kuzucuk meeliyor...
pamuk meeliyor...
kara miyavliyor...
************************************
hayvan ciftliginde 2kedi, 1kopek ve 2kuzu yasiyor.
*****************************

Özellik

std::variant

std::optional

std::any

Türler

Belirli bir dizi türden birini saklar.

Tek bir tür veya boş değeri (nullopt) saklar.

Herhangi bir türü dinamik olarak saklar.

Depolama

En büyük türün boyutuna göre depolama yapar.

Sakladığı türün boyutuna göre depolama yapar.

Dinamik bellek kullanarak depolama yapar.

Kopyalama

deep copy

deep copy

deep copy

Nullable

std::monostate ile temsil edilebilir.

std::nullopt ile temsil edilir.

Nullable özellik sunmaz.

Tür Güvenliği

Tür güvenliği sağlar.

Tür güvenliği sağlar.

Tür güvenliği sağlamaz; typeid kullanılır.

Kullanım

Çoklu türleri yönetmek için kullanılır.

Tek bir tür veya yokluk durumlarını ifade eder.

Dinamik olarak tür değişimi yapılabilir.

Perfomans

Genellikle std::optional'dan daha fazla bellek kullanabilir.

Daha az bellek kullanır.

Daha fazla bellek ve zaman maliyeti olabilir.

Erişim

std::get, std::visit gibi fonksiyonlar kullanılır.

operator*, value(), has_value() kullanılır.

std::any_cast ile erişim sağlanır.


 
 
 

Recent Posts

See All
C++ Dilinde [[nodiscard]] attribute

C++’ta [[attribute]] (öznitelik) mekanizması, kod hakkında ek bilgi sağlayarak derleyicinin uyarılar vermesine, belirli optimizasyonlar...

 
 
 
C++ Dilinde constinit

C++20'de tanıtılan önemli bir özellik `constinit`'dir; bu özellik, statik depolama süresine sahip değişkenlerin derleme zamanında...

 
 
 

Commentaires


bottom of page