19 Haziran 2020 Cuma

std::variant Sınıfı

Giriş
Şu satırı dahil ederiz.
#include <variant>
Bu sınıf C++17 ile geliyor. Bu sınıftan kalıtmak mümkün.

Variant'a Yazma
Yazma için özel bir metod gerekmiyor. Normak atama yapılabilir

Variant'tan Okuma
variant'ın sakladığı değere erişmek için std::get kullanılır

Constructor
Elimizde bir variant olsun.
using IntOrString = std::variant<int, std::string>;
Bu variant için bir map tanımlayabiliriz.Şöyle yaparız.
std::map<std::string, IntOrString> myMap;
myMap ["first_key"]  = 10;
myMap ["second_key"] = "stringValue";
Copy Constructor
variant aslında bir nesne. Aynı tipi içeren iki variant kopyalanabilir. Elimizde bir variant olsun.
template<typename T>
using VectorOrSimple = std::variant<T, std::vector<T>>;
Bu variant bir nesne içinde kullanılabilir ve kopyalanabilir. Şöyle yaparız.
template<typename T>
using VectorOrSimple = std::variant<T, std::vector<T>>;

class A {
public:
  explicit A (const VectorOrSimple<int>& arg) :
    member (arg) {
    ...  
  }

private:
  const VectorOrSimple<int> member;
};
Şöyle yaparız.
int simple = 1;
A a1 (simple);
swap metodu
Örnek
Şöyle yaparız
#include <variant>

int main(){
  using V = std::variant<int, double>;
  V a = 5;
  V b = 5.6;

  a.swap(b);
}
which metodu
Şöyle yaparız. Lambda int parametre alıp void dönen metod ile aynıdır. void döndüğü için lambda'nın döndürdüğü değer dikkate alınmaz. Dolayısıyla çıktı olarak 1 alırız.
using variant_t = std::variant<
    std::function<std::future<void>(int)>,
    std::function<void(int)>
>;
auto f1 = [](int) { return std::async([] { return 1; }); };

variant_t v1(std::move(f1));
auto idx1 = v1.index(); //equals 1.

Diğer
std::variant heap kullanmaz
Nesne std::variant içindeki alanda saklanır. Açıklaması şöyle.
As with unions, if a variant holds a value of some object type T, the object representation of T is allocated directly within the object representation of the variant itself. Variant is not allowed to allocate additional (dynamic) memory.
State Değişimi
Açıklaması şöyle.
std::variant can enter a state called "valueless by exception".

As I understand, the common cause of this is if a move assignment throws an exception. The variant's old value isn't guaranteed to be present anymore, and neither is the intended new value.
Açıklaması şöyle.
"valueless by exception" refers to a specific scenario where you need to change the type stored in the variant. That necessarily requires 1) destroying the old value and then 2) creating the new one in its place. If 2) fails, you have no way to go back (without undue overhead unacceptable to the committee).

optional doesn't have this problem. If some operation on the object it contains throws an exception, so be it. The object is still there. That doesn't mean that the object's state is still meaningful - it's whatever the throwing operation leaves it in. Hopefully that operation has at least the basic guarantee.
Boost ile Farkı
std::variant sınıfı boost::variant'tan farklı olarak move constructor'ın exception atmasını kabul ediyor.
Yani move constructor şöyle olabilir.
B(B&&) {}
Şöyle olabilir.
B(B&&) noexcept {};
Şöyle olabilir.
B(B&&) = default;
std::variant array ile çalışmaz
Şu kod derlenmez.
std::variant<uint64_t, uint8_t[8]> v
Alternatif
Elimizde string,boolean,integer ve double tiplerini string olarak saklayabilen şöyle bir yapı olsun
class Parameter {
public:
  enum KnownTypes { STRING = 0, BOOLEAN, INTEGER, DOUBLE };

  std::string key;
  std::string value;
  KnownTypes type;
};
Bu yapıdan okuyabilmek için şöyle yaparız.
class Configuration {
public:
  template <class RETURNTYPE>
  RETURNTYPE get(std::string const& key) {
    // get parameter(eg. get cached value or from db...)
std::map<std::string, Parameter> map{
  {"int", Parameter{"int", "100", Parameter::KnownTypes::INTEGER}},
  {"string", Parameter{"string", "string_value", Parameter::KnownTypes::STRING}},
  {"throwMe", Parameter{"throwMe", "throw", Parameter::KnownTypes::DOUBLE}},
  {"bool", Parameter{"bool", "Y", Parameter::KnownTypes::BOOLEAN}}};
  const Parameter& parameter = map.at(key);

  bool isMatchingType = false;
  switch (parameter.type) {
    case Parameter::STRING:
      isMatchingType = std::is_same<RETURNTYPE, std::string>::value;
      break;
    case Parameter::BOOLEAN:
      isMatchingType = std::is_same<RETURNTYPE, bool>::value;
      break;
    case Parameter::INTEGER:
      isMatchingType = std::is_same<RETURNTYPE, int>::value;
      break;
    case Parameter::DOUBLE:
      isMatchingType = std::is_same<RETURNTYPE, double>::value;
      break;
    };

    if (!isMatchingType)
      throw "Tthe requested return type does not match";

    return getImpl<RETURNTYPE>(parameter);
  }

Impl metodlarını şöyle yaparız. Burada explicit template specialization kullanılıyor.
private:
  template <class RETURNTYPE>
  RETURNTYPE getImpl(const Parameter& parameter);
};

template <>
std::string Configuration::getImpl<std::string>(const Parameter& parameter) {
  return parameter.value;
}

template <>
bool Configuration::getImpl<bool>(const Parameter& parameter) {
  return parameter.value == "Y";
}

template <>
int Configuration::getImpl<int>(const Parameter& parameter) {
  return std::stoi(parameter.value);
}



Hiç yorum yok:

Yorum Gönder