C++17 - std::variant

C++17 ile beraber gelen yardımcı sınıflardan biri de union’un typesafe alternatifi olan std::variant’tır.
İçinde birden çok türden sadece bir değişken tutmaya yarar. Benim bu zamana kadar bir ihtiyacım olmadı ama farklı türden nicknamelerin tutulduğunu varsayarak şöyle bir örnek düşünebiliriz;

int main(int argc, char* argv[])
{
std::variant<int, std::string, double> nicknames = 3;

std::cout << "type :" << nicknames.index() << " " << std::get<int>(nicknames) << std::endl;

if (auto iptr = std::get_if<int>(&nicknames); iptr)
std::cout << "*iptr :" << *iptr << std::endl;


if (std::holds_alternative<int>(nicknames))
std::cout << "holding type is integer" << std::endl;

nicknames = "cppturkey";

std::cout << "type :" << nicknames.index() << " " << std::get<std::string>(nicknames) << std::endl;

if (auto iptr = std::get_if<std::string>(&nicknames); iptr)
std::cout << "*iptr :" << *iptr << std::endl;


if (std::holds_alternative<std::string>(nicknames))
std::cout << "holding type is integer" << std::endl;
}

std::variant’a değer ataması ve değere ulaşılması örnekteki gibi yapılabilir. Eğer variant herhangi bir şekilde initialize edilmemiş ise o halde ilk türe ait default constructor çağıralarak değişken initialize edilir. Eğer ilk türün default constructorı yok ise std::monostate argümanı geçirilerek std::variantın oluşturulması sağlanabilir aksi takdirde derleme hatası alınır.

struct NoDefaultCtor
{

NoDefaultCtor(int a)
{}

~NoDefaultCtor(){};
};


int main(int argc, char* argv[])
{

//std::variant<NoDefaultCtor, int> variant1; compile error

std::variant<std::monostate, NoDefaultCtor, int> variant1;

return 0;
}

Ayrıca örnekte görüldüğü üzere ilkel türler haricinde daha karmaşık türlerde şablon parametresi olarak geçilebilir. Bir türden diğer türe geçişte eski türe ait destuctor çağırılarak iç nesne yok edilir.

std::visit

std::visit ise std::variant üzerinde o an aktif olan tür için, fonksiyon çağırmamıza olanak sağlayan bir STL fonksiyonudur. Örneğimizde de, C++14 ile beraber gelen generic lambdalar ise her tür için çalışabilen bir fonksiyon set etmemize imkan sağlamıştır.

std::variant<int, std::string, double> nicknames = 3;


std::visit([](const auto &nickname)
{
std::cout << nickname << std::endl;
},
nicknames);

nicknames = "cppturkey";

std::visit([](const auto &nickname)
{
std::cout << nickname << std::endl;
},
nicknames);

Benzer şekilde std::variant da boost::variant tecrübesinden faydanılarak standart kütüphaneye eklenmiştir. std::visit kullanılarak üstteki örneğe benzer şekilde polimorfizme daha az maliyetli (runtime da, vtables olmadan) bir alternatif olarak kullanılabilir. Daha önce bu yazıda yaptığımız örnek üzerinden gidersek;

class Hayvan
{

public:

void turIsmi(){cout << "Tür ismi: Hayvan" << endl;};
};

class Aslan: public Hayvan
{
public:
void turIsmi() {cout << "Tür ismi: Aslan" << endl;};

};

class Kaplan: public Hayvan
{
public:
void turIsmi() {cout << "Tür ismi: Kaplan" << endl;};

};

class KotuKediSerafettin: public Hayvan
{
public:
void turIsmi() {cout << "Tür ismi: Şero" << endl;};

};

int main(int argc, char* argv[])
{
std::vector<std::variant<Aslan, Kaplan, KotuKediSerafettin>> animals {
Aslan(),
KotuKediSerafettin(),
Kaplan(),
Aslan()
};

for (auto& hyvn : animals)
std::visit([] (auto &el) {
el.turIsmi();
}, hyvn);

return 0;
}

şeklinde bitirebiliriz. :)