23 標簽聯(lián)合

  • 這里開發(fā)一個類似于std::variant的類模板Variant,Variant提供了比union更好的類型安全捶索,其行為如下
#include "variant.hpp"
#include <iostream>
#include <string>

int main()
{
  Variant<int, double, std::string> v(42);
  if (v.is<int>()) std::cout << v.get<int>(); // 42
  v = "hi";
  std::cout << v.get<std::string>(); // hi
}

存儲

  • Variant的首要設計是管理活動值的存儲,不同類型可能有不同的大小和對齊要考慮问潭,一個簡單(盡管低效)的存儲機制是直接使用一個tuple。此外還需要存儲一個discriminator作為tuple的索引沙峻,比如discriminator為0時睦授,get<0>(storage)訪問活動值
template<typename... Types>
class Variant {
 public:
  Tuple<Types...> storage;
  unsigned char discriminator;
};
  • 但這種做法導致Variant需要所有類型大小總和的空間两芳,即使某個時刻只有一個活動值摔寨。更好的做法是重疊每個可能類型的空間,這可以通過遞歸拆分variant為首部和尾部來實現(xiàn)
template<typename... Types>
union VariantStorage;

template<typename Head, typename... Tail>
union VariantStorage<Head, Tail...> {
  Head head;
  VariantStorage<Tail...> tail;
};

template<>
union VariantStorage<> {};
  • 這里的union保證足夠的大小和對齊來存儲參數(shù)列表的任意類型怖辆,但這個union本身很難使用是复,因為大多用來實現(xiàn)Variant的技術將使用繼承删顶,而這對union是不允許的。取代做法是用最底層的字符數(shù)組來存儲淑廊,這個數(shù)組足夠大到存儲任何類型(通過一個計算最大類型的type traits)逗余,并對任何類型有合適的對齊(通過alignas指定符
#include <new> // for std::launder()

template<typename... Types>
class VariantStorage {
  using LargestT = LargestType<Typelist<Types...>>; 
  alignas(Types...) unsigned char buffer[sizeof(LargestT)];
  unsigned char discriminator = 0;
 public:
  unsigned char getDiscriminator() const { return discriminator; }
  void setDiscriminator(unsigned char d) { discriminator = d; }
  void* getRawBuffer() { return buffer; }
  const void* getRawBuffer() const { return buffer; }

  template<typename T>
  T* getBufferAs() { return std::launder(reinterpret_cast<T*>(buffer)); }

  template<typename T>
  const T* getBufferAs() const
  {
    return std::launder(reinterpret_cast<const T*>(buffer));
  }
};

設計

  • 接著設計Variant類型本身。和Tuple一樣季惩,使用繼承對每個Types中的類型提供行為录粱。不同于Tuple的是,這些基類沒有存儲画拾。相反啥繁,每個基類使用CRTP通過最派生的類型訪問共享的Variant存儲。定義如下VariantChoice類模板,它提供活動值為類型T時字符數(shù)組上所需的核心操作
template<typename T, typename... Types>
class VariantChoice {
  using Derived = Variant<Types...>;
  Derived& getDerived() { return *static_cast<Derived*>(this); }
  const Derived& getDerived() const
  {
    return *static_cast<const Derived*>(this);
  }
 protected:
  constexpr static unsigned Discriminator =
    FindIndexOfT<Typelist<Types...>, T>::value + 1;
 public:
  VariantChoice() {}
  // see variantchoiceinit.hpp
  VariantChoice(const T& value);
  VariantChoice(T&& value);

  bool destroy(); // see variantchoicedestroy.hpp
  // see variantchoiceassign.hpp
  Derived& operator=(const T& value);
  Derived& operator=(T&& value);
};
  • 元函數(shù)FindIndexOfT用來找出Types中類型T的位置青抛,實現(xiàn)如下
template<typename List, typename T, unsigned N = 0,
  bool Empty = IsEmpty<List>::value>
struct FindIndexOfT;

// recursive case:
template<typename List, typename T, unsigned N>
struct FindIndexOfT<List, T, N, false>
: public IfThenElse<std::is_same<Front<List>, T>::value,
  std::integral_constant<unsigned, N>,
  FindIndexOfT<PopFront<List>, T, N+1>>
{};

// basis case:
template<typename List, typename T, unsigned N>
struct FindIndexOfT<List, T, N, true>
{};
  • Variant繼承于VariantStorage和VariantChoice
template<typename... Types>
class Variant : private VariantStorage<Types...>,
  private VariantChoice<Types, Types...>...
{
  template<typename T, typename... OtherTypes>
  friend class VariantChoice; // enable CRTP
  ...
};
  • 對于一個
Variant<int, double, std::string>
  • 生成下列VariantChoice基類
VariantChoice<int, int, double, std::string>,
VariantChoice<double, int, double, std::string>,
VariantChoice<std::string, int, double, std::string>
  • 這三個基類對應的discriminator值將為1旗闽、2、3蜜另。當Variant的存儲成員discriminator匹配一個特定基類的VariantChoice::Discriminator時适室,基類負責管理活動值
  • discriminator值為0被保留用于Variant不包含值的情況,這是一種奇態(tài)(odd state)举瑰,只有在分配期間拋出異常時才能觀察到
  • Variant完整定義如下
template<typename... Types>
class Variant : private VariantStorage<Types...>,
  private VariantChoice<Types, Types...>...
{
  template<typename T, typename... OtherTypes>
  friend class VariantChoice;
 public:
  template<typename T> bool is() const; // see variantis.hpp

  // see variantget.hpp
  template<typename T> T& get() &;
  template<typename T> const T& get() const&;
  template<typename T> T&& get() &&;

  // see variantvisit.hpp:
  template<typename R = ComputedResultType, typename Visitor>
  VisitResult<R, Visitor, Types&...> visit(Visitor&& vis) &;

  template<typename R = ComputedResultType, typename Visitor>
  VisitResult<R, Visitor, const Types&...> visit(Visitor&& vis) const&;

  template<typename R = ComputedResultType, typename Visitor>
  VisitResult<R, Visitor, Types&&...> visit(Visitor&& vis) &&;

  using VariantChoice<Types, Types...>::VariantChoice...;

  Variant(); // see variantdefaultctor.hpp
  Variant(const Variant& source); // see variantcopyctor.hpp
  Variant(Variant&& source); // see variantmovector.hpp

  template<typename... SourceTypes>
  Variant(const Variant<SourceTypes...>& source); // see variantcopyctortmpl.hpp

  template<typename... SourceTypes>
  Variant(Variant<SourceTypes...>&& source); // see variantmovectortmpl.hpp

  using VariantChoice<Types, Types...>::operator=...;

  Variant& operator= (const Variant& source); // see variantcopyassign.hpp
  Variant& operator= (Variant&& source); // see variantmoveassign.hpp

  template<typename... SourceTypes>
  Variant& operator= (const Variant<SourceTypes...>& source); // see variantcopyassigntmpl.hpp

  template<typename... SourceTypes>
  Variant& operator= (Variant<SourceTypes...>&& source); // see variantmoveassigntmpl.hpp

  bool empty() const; // see variantempty.hpp

  ~Variant() { destroy(); }
  void destroy(); // see variantdestroy.hpp
};

查詢

  • is()成員函數(shù)定義如下捣辆,用于確定Variant當前是否存儲一個T類型值
// variant/variantis.hpp

template<typename... Types>
  template<typename T>
bool Variant<Types...>::is() const
{
  return this->getDiscriminator() ==
    VariantChoice<T, Types...>::Discriminator;
}

// v.is<int>()將確定v的活動值是否為int
  • 如果查找的類型未在列表中在找到,F(xiàn)indIndexOfT將不會包含一個value成員嘶居,于是VariantChoice基類將實例化失敗罪帖,從而造成is()的編譯錯誤,這樣就可以防止用戶請求無法存儲的類型

提取

  • get()成員函數(shù)通過接受一個類型邮屁,提取一個該類型存儲值的引用整袁,并且僅當Variant的活動值是該類型時才有效。當Variant未保存一個值(即discriminator為0)時佑吝,get()拋出一個EmptyVariant異常
// variant/variantget.hpp

#include <exception>
class EmptyVariant : public std::exception {};

template<typename... Types>
  template<typename T>
T& Variant<Types...>::get() &
{
  if (empty()) throw EmptyVariant();
  assert(is<T>());
  return *this->template getBufferAs<T>();
}

初始化

  • 從一個類型的值初始化一個Variant坐昙,通過一個VariantChoice構造函數(shù)實現(xiàn),這個構造函數(shù)接受一個T類型的值
// variant/variantchoiceinit.hpp

template<typename T, typename... Types>
VariantChoice<T, Types...>::VariantChoice(const T& value)
{
  // getDerived()使用CRTP來操作派生類
  new(getDerived().getRawBuffer()) T(value); // 用placement new將value置入buffer
  // 設置派生類的discriminator為基類的Discriminator來表示存儲的類型
  getDerived().setDiscriminator(Discriminator);
}

template<typename T, typename... Types>
VariantChoice<T, Types...>::VariantChoice(T&& value)
{
  new(getDerived().getRawBuffer()) T(std::move(value));
  getDerived().setDiscriminator(Discriminator);
}
  • 最終的目標是能從一個任意類型的值初始化Variant芋忿,甚至可以隱式轉換
Variant<int, double, string> v("hello"); // 隱式轉換為string
  • 為此使用using聲明把VariantChoice的構造函數(shù)引入到Variant中
using VariantChoice<Types, Types...>::VariantChoice...;
  • 這個using聲明為Types中的每個類型生成一個Variant的構造函數(shù)
// Variant<int, double, string>的構造函數(shù)實際是
Variant(const int&);
Variant(int&&);
Variant(const double&);
Variant(double&&);
Variant(const string&);
Variant(string&&);

析構

  • 當Variant初始化時炸客,一個值構造到它的buffer中,destroy處理那個值的析構
// variant/variantchoicedestroy.hpp

template<typename T, typename... Types>
bool VariantChoice<T, Types...>::destroy()
{
  if (getDerived().getDiscriminator() == Discriminator)
  {
    // 如果匹配則調(diào)用placement delete
    getDerived().template getBufferAs<T>()->~T();
    return true;
  }
  return false;
}
  • 只有當discriminator匹配時戈钢,VariantChoice::destroy()操作是有用的痹仙。但通常希望不考慮當前活動的類型,直接銷毀存儲在Variant中的值殉了,因此Variant::destroy()將調(diào)用基類中所有的VariantChoice::destroy()
// variant/variantdestroy.hpp

template<typename... Types>
void Variant<Types...>::destroy()
{
  // 調(diào)用所有的VariantChoice::destroy()开仰,至多一個成功
  bool results[] = { VariantChoice<Types, Types...>::destroy()...};
  this->setDiscriminator(0); // 表示不再存儲一個值,Variant為空
}
  • 數(shù)組results的作用是提供一個能使用初始化列表的上下文,C++17中可以使用折疊表達式來消除對數(shù)組results的需要
// variant/variantdestroy17.hpp

template<typename... Types>
void Variant<Types...>::destroy()
{
  (VariantChoice<Types, Types...>::destroy(), ...);
  this->setDiscriminator(0);
}

賦值

  • 對于同類型直接賦值众弓,對于不同類型則需要析構現(xiàn)有值恩溅,再用placement new重新初始化新類型的值
// variant/variantchoiceassign.hpp

template<typename T, typename... Types>
auto VariantChoice<T, Types...>::operator=(const T& value) -> Derived&
{
  if (getDerived().getDiscriminator() == Discriminator) // 同類型的值
  {
    *getDerived().template getBufferAs<T>() = value;
  }
  else // 不同類型的值
  {
    getDerived().destroy(); // 用Variant::destroy()析構現(xiàn)有值
    new(getDerived().getRawBuffer()) T(value); // 用placement new初始化T類型新值
    getDerived().setDiscriminator(Discriminator);
  }
  return getDerived();
}

template<typename T, typename... Types>
auto VariantChoice<T, Types...>::operator=(T&& value) -> Derived&
{
  if (getDerived().getDiscriminator() == Discriminator) // 同類型的值
  {
    *getDerived().template getBufferAs<T>() = std::move(value);
  }
  else // 不同類型的值
  {
    getDerived().destroy();
    new(getDerived().getRawBuffer()) T(std::move(value));
    getDerived().setDiscriminator(Discriminator);
  }
  return getDerived();
}
  • 和初始化一樣,可以在Variant中使用using聲明來繼承賦值操作
using VariantChoice<Types, Types...>::operator=...;
  • 對于不同類型谓娃,賦值有兩步脚乡,先析構再初始化,這將有三個需要考慮的問題:自賦值滨达、異常奶稠、std::launder
  • 自賦值發(fā)生情況如下。如果使用兩步賦值捡遍,源值將在拷貝前會被析構窒典,導致內(nèi)存崩潰。但好在自賦值意味著discriminator匹配稽莉,所以會使用同類型賦值瀑志,因此不會出問題
v = v.get<T>()
  • 如果現(xiàn)有值的銷毀已經(jīng)完成,但是新值的初始化拋出異常污秆,Variant::destroy()將discriminator值重置為0劈猪,表示Variant未存儲值
// variant/variantexception.cpp

class CopiedNonCopyable : public std::exception {};

class NonCopyable {
 public:
  NonCopyable() = default;

  NonCopyable(const NonCopyable&)
  {
    throw CopiedNonCopyable();
  }

  NonCopyable(NonCopyable&&) = default;

  NonCopyable& operator=(const NonCopyable&)
  {
    throw CopiedNonCopyable();
  }

  NonCopyable& operator=(NonCopyable&&) = default;
};

int main()
{
  Variant<int, NonCopyable> v(42);
  try
  {
    NonCopyable nc;
    v = nc;
  }
  catch (CopiedNonCopyable)
  {
    std::cout << "Copy assignment of NonCopyable failed." << '\n';
    if (!v.is<int>() && !v.is<NonCopyable>())
    {
      std::cout << "Variant has no value.";
    }
  }
}

// 程序輸出為
Copy assignment of NonCopyable failed.
Variant has no value.
  • 訪問一個沒有值的variant將拋出EmptyVariant異常,以允許程序從這個異常條件修復
  • empty()成員函數(shù)用于檢查Variant是否為空
// variant/variantempty.hpp

template<typename... Types>
bool Variant<Types...>::empty() const
{
  return this->getDiscriminator() == 0;
}
  • 第三個問題是標準化委員會在C++ 17標準化過程結束時才意識到的一個微妙問題良拼。編譯器通常希望產(chǎn)生高性能代碼战得,而提高生成代碼性能的主要機制是避免從內(nèi)存到寄存器的重復的數(shù)據(jù)復制。為此編譯器必須做一些假設庸推,其中一種假設是某些類型的數(shù)據(jù)在其生命周期內(nèi)是不可變的常侦,這包括const數(shù)據(jù)、引用(可以初始化贬媒,但之后不修改)以及存儲在多態(tài)對象中的一些bookkeeping data(用于調(diào)度虛擬函數(shù)聋亡、定位虛擬基類、以及處理typeid和dynamic_cast操作符)
  • 上面兩步分配過程的問題是际乘,它以一種編譯器可能無法識別的方式悄悄地結束一個對象的生命周期坡倔,并在同一位置開始另一個對象的生命周期。因此脖含,編譯器可能假定它從Variant對象的先前狀態(tài)獲取的值仍然有效罪塔,而實際上placement new的初始化會使其無效,這就導致編譯具有不可變數(shù)據(jù)成員的Variant時偶爾會產(chǎn)生無效結果养葵。這種bug通常很難追蹤(一部分原因是它們很少發(fā)生征堪,另一部分原因是它們在源代碼中并不真正可見)
  • C++17中,這個問題的解決方案是使用std::launder关拒,它返回實參所表示地址的對象的指針佃蚜,使得編譯器不再假設原有值有效咳榜,從而起到修復的效果
  • 還有一些方法可以做得更好,比如額外添加一個指向buffer的指針成員爽锥,并在每次使用placement new賦值一個新值時獲得已清洗的地址,但這些方法會使代碼變得復雜畔柔,難以維護

訪問者(Visitor)

  • 檢查一個Variant中的所有可能類型會造成一個冗長的if語句鏈
if (v.is<int>())
{
  std::cout << v.get<int>();
}
else if (v.is<double>())
{
  std::cout << v.get<double>();
}
else
{
  std::cout << v.get<string>();
}
  • 為此需要引入一個函數(shù)模板
template<typename V, typename Head, typename... Tail>
void printImpl(const V& v)
{
  if (v.template is<Head>())
  {
    std::cout << v.template get<Head>();
  }
  else if constexpr (sizeof...(Tail) > 0)
  {
    printImpl<V, Tail...>(v);
  }
}

template<typename... Types>
void print(const Variant<Types...>& v)
{
  printImpl<Variant<Types...>, Types...>(v);
}

int main()
{
  Variant<int, short, float, double> v(3.14);
  print(v);
}
  • 但對于一個相對簡單的操作來說氯夷,這是一個相當大的代碼量。為了簡化這點靶擦,可以用visit()來擴展Variant腮考,它接受一個仿函數(shù)或者lambda,接受的參數(shù)就相當于訪問者的角色
v.visit([] (const auto& value) { std::cout << value; });
  • variantVisitImpl()遍歷Variant的類型玄捕,檢查活動值是否具有給定類型踩蔚,然后在找到適當類型時進行操作
template<typename R, typename V, typename Visitor,
  typename Head, typename... Tail>
R variantVisitImpl(V&& variant, Visitor&& vis, Typelist<Head, Tail...>) {
  if (variant.template is<Head>())
  {
    return static_cast<R>(
      std::forward<Visitor>(vis)(
        std::forward<V>(variant).template get<Head>()));
  }
  else if constexpr (sizeof...(Tail) > 0)
  {
    return variantVisitImpl<R>(std::forward<V>(variant),
      std::forward<Visitor>(vis),
      Typelist<Tail...>());
  }
  else
  {
    throw EmptyVariant();
  }
}
  • visit()的實現(xiàn)直接委托給variantVisitImpl()即可,傳遞Variant本身枚粘,轉發(fā)訪問者馅闽,并提供完整的類型列表。三種實現(xiàn)分別在this對象作為Variant&馍迄、const Variant&或Variant&&傳遞時被調(diào)用福也,類成員函數(shù)后加引用限定符的用法可參考《Effective Modern C++》條款12
// variant/variantvisit.hpp

template<typename... Types>
  template<typename R, typename Visitor>
VisitResult<R, Visitor, Types&...>
Variant<Types...>::visit(Visitor&& vis)& {
  using Result = VisitResult<R, Visitor, Types&...>;
  return variantVisitImpl<Result>(*this, std::forward<Visitor>(vis),
    Typelist<Types...>());
}

template<typename... Types>
  template<typename R, typename Visitor>
VisitResult<R, Visitor, const Types&...>
Variant<Types...>::visit(Visitor&& vis) const& {
  using Result = VisitResult<R, Visitor, const Types &...>;
  return variantVisitImpl<Result>(*this, std::forward<Visitor>(vis),
    Typelist<Types...>());
}

template<typename... Types>
  template<typename R, typename Visitor>
VisitResult<R, Visitor, Types&&...>
Variant<Types...>::visit(Visitor&& vis) && {
  using Result = VisitResult<R, Visitor, Types&&...>;
  return variantVisitImpl<Result>(std::move(*this),
    std::forward<Visitor>(vis),
    Typelist<Types...>());
}
  • 這里還需要實現(xiàn)VisitResult元函數(shù)。visit()的結果類型仍不能確定攀圈,比如接受如下lambda暴凑,其返回類型取決于輸入的參數(shù)類型
[] (const auto& value) { return value + 1; }
  • visit()設置一個模板參數(shù)R,其默認實參為ComputedResultType
template<typename R = ComputedResultType, typename Visitor>
VisitResult<R, Visitor, Types&...> visit(Visitor&& vis) &;
  • 現(xiàn)在希望顯式指定返回結果的類型時赘来,將R設為返回類型
// visit的返回類型是Variant<int, double>中的類型
v.visit<Variant<int, double>>([] (const auto& value) { return value + 1; });
  • 不顯式指定時现喳,將使用R的默認實參ComputedResultType(一個只需聲明無需實現(xiàn)的類,只是用作標簽)
class ComputedResultType;
  • 元函數(shù)VisitResult的實現(xiàn)如下
// 顯式指定返回類型的情形
template<typename R, typename Visitor, typename... ElementTypes>
class VisitResultT {
 public:
  using Type = R;
};

// 對使用默認實參ComputedResultType的情形特化一個版本
template<typename Visitor, typename… ElementTypes>
class VisitResultT<ComputedResultType, Visitor, ElementTypes…> {
  ... // 實現(xiàn)見后
}

template<typename R, typename Visitor, typename... ElementTypes>
using VisitResult = typename VisitResultT<R, Visitor, ElementTypes...>::Type;
  • 對于通用類型(common type)犬辰,C++已經(jīng)有了一個合理的概念:在三元表達式b ? x : y中嗦篱,結果類型是x和y之間的通用類型,例如x為int幌缝,y為double默色,則通用類型為double。由此實現(xiàn)CommonType如下
using std::declval;

template<typename T, typename U>
class CommonTypeT {
 public:
  using Type = decltype(true? declval<T>() : declval<U>());
};

template<typename T, typename U>
using CommonType = typename CommonTypeT<T, U>::Type;
  • 特化的VisitResultT得到對Variant的每個類型調(diào)用訪問者時生成的結果類型的通用類型狮腿,實現(xiàn)如下
// 對T類型值調(diào)用訪問者時生成的結果類型
template<typename Visitor, typename T>
using VisitElementResult = decltype(declval<Visitor>()(declval<T>()));

// 對每個元素類型調(diào)用訪問者時的通用結果類型
template<typename Visitor, typename... ElementTypes>
class VisitResultT<ComputedResultType, Visitor, ElementTypes...> {
  using ResultTypes = // 每個元素調(diào)用訪問者時生成的結果類型
    Typelist<VisitElementResult<Visitor, ElementTypes>...>;
 public:
  using Type = // 所有結果類型的通用類型
    Accumulate<PopFront<ResultTypes>, CommonTypeT, Front<ResultTypes>>;
};
  • 標準庫提供了std::common_type腿宰,它組合了CommonTypeT和Accumulate以對任意數(shù)量類型生成通用類型,因此特化的VisitResultT可以更簡單地實現(xiàn)如下
template<typename Visitor, typename... ElementTypes>
class VisitResultT<ComputedResultType, Visitor, ElementTypes...> {
 public:
  using Type =
    std::common_type_t<VisitElementResult<Visitor, ElementTypes>...>;
};
  • 下例說明了visit()接受一個訪問者時的返回類型
Variant<int, short, double, float> v(3.14);
auto result = v.visit([] (const auto& value) { return value + 1; });
std::cout << typeid(result).name(); // double

默認構造

  • Variant應該允許默認構造缘厢,否則每次使用都要指定初始值乱投。對于默認構造的方式挂谍,可能會想到設置discriminator為0來表示沒有存儲值,但這樣的空Variant不能被訪問或找到任何要提取的值,并且將空Variant的異常狀態(tài)提升到了通用狀態(tài)
  • 一個避免引入空Variant的方法是初始化類型列表中首個類型的一個值
// variant/variantdefaultctor.hpp

template<typename... Types>
Variant<Types...>::Variant()
{
  *this = Front<Typelist<Types...>>();
}
  • 這個方法是簡單和可預測的
Variant<int, double> v;
if (v.is<int>()) std::cout << v.get<int>() <<'\n'; // 0(int類型)
Variant<double, int> v2;
if (v2.is<double>()) std::cout << v2.get<double>(); // 0(double類型)

拷貝和移動構造

  • 要拷貝一個Variant需要確定它當前存儲的類型,將該值拷貝構造到字符數(shù)組中略荡,并設置discriminator,這兩點在VariantChoice的拷貝賦值運算符中已提供,通過對要拷貝的Variant使用visit()即可簡潔緊湊地實現(xiàn)目的
// variant/variantcopyctor.hpp

template<typename... Types>
Variant<Types...>::Variant(const Variant& source)
{
  if (!source.empty())
  {
    source.visit([&] (const auto& value) {
      *this = value; // 從VariantChoice繼承的拷貝賦值運算符
    });
  }
}
  • 移動構造同理
// variant/variantmovector.hpp

template<typename... Types>
Variant<Types...>::Variant(Variant&& source)
{
  if (!source.empty())
  {
    std::move(source).visit([&] (auto&& value) { *this = std::move(value); });
  }
}
  • 基于visit的實現(xiàn)也適用于模板化形式的拷貝和移動構造
// variant/variantcopyctortmpl.hpp

template<typename... Types>
  template<typename... SourceTypes>
Variant<Types...>::Variant(const Variant<SourceTypes...>& source)
{
  if (!source.empty())
  {
    source.visit([&] (const auto& value) { *this = value; });
  }
}


// variant/variantmovectortmpl.hpp

template<typename... Types>
  template<typename... SourceTypes>
Variant<Types...>::Variant(Variant<SourceTypes...>&& source)
{
  if (!source.empty())
  {
    std::move(source).visit([&](auto&& value) { *this = std::move(value); });
  }
}
  • 下面是Variant的構造和賦值的示例
Variant<short, float, const char*> v1((short)42);
Variant<int, std::string, double> v2(v1); // short到int的整型提升
std::cout << v2.get<int>(); // 42(int類型)

v1 = 3.14f;
Variant<double, int, std::string> v3(std::move(v1)); // float到double的浮點提升
std::cout << v3.get<double>(); // 3.14(double類型)

v1 = "hello";
Variant<double, int, std::string> v4(std::move(v1)); // const char*到std::string的轉換
std::cout << v4.get<std::string>(); // hello

賦值運算符

  • 賦值運算符與拷貝和移動構造類似
// variant/variantcopyassign.hpp

template<typename... Types>
Variant<Types...>& Variant<Types...>::operator=(const Variant& source)
{
  if (!source.empty())
  {
    source.visit([&] (const auto& value) { *this = value; });
  }
  else
  {
    destroy(); // 源Variant為空時析構目標亦渗,隱式設置discriminator為0
  }
  return *this;
}


// variant/variantmoveassign.hpp

template<typename... Types>
Variant<Types...>& Variant<Types...>::operator= (Variant&& source)
{
  if (!source.empty())
  {
    std::move(source).visit([&] (auto&& value) { *this = std::move(value); });
  }
  else
  {
    destroy();
  }
  return *this;
}


// variant/variantcopyassigntmpl.hpp

template<typename... Types>
  template<typename... SourceTypes>
Variant<Types...>& Variant<Types...>::operator=(const Variant<SourceTypes...>& source)
{
  if (!source.empty())
  {
    source.visit([&] (const auto& value) { *this = value; });
  }
  else
  {
    destroy();
  }
  return *this;
}


// variant/variantmoveassigntmpl.hpp

template<typename... Types>
  template<typename... SourceTypes>
Variant<Types...>& Variant<Types...>::operator=(Variant<SourceTypes...>&& source)
{
  if (!source.empty())
  {
    std::move(source).visit([&] (auto&& value) { *this = std::move(value); });
  }
  else
  {
    destroy();
  }
  return *this;
}
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市汁尺,隨后出現(xiàn)的幾起案子法精,更是在濱河造成了極大的恐慌,老刑警劉巖痴突,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搂蜓,死亡現(xiàn)場離奇詭異,居然都是意外死亡辽装,警方通過查閱死者的電腦和手機帮碰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拾积,“玉大人殉挽,你說我怎么就攤上這事⊥厍桑” “怎么了此再?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長玲销。 經(jīng)常有香客問我输拇,道長,這世上最難降的妖魔是什么贤斜? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任策吠,我火速辦了婚禮,結果婚禮上瘩绒,老公的妹妹穿的比我還像新娘猴抹。我一直安慰自己,他們只是感情好锁荔,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布蟀给。 她就那樣靜靜地躺著,像睡著了一般阳堕。 火紅的嫁衣襯著肌膚如雪跋理。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天恬总,我揣著相機與錄音前普,去河邊找鬼。 笑死壹堰,一個胖子當著我的面吹牛拭卿,可吹牛的內(nèi)容都是我干的骡湖。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼峻厚,長吁一口氣:“原來是場噩夢啊……” “哼响蕴!你這毒婦竟也來了?” 一聲冷哼從身側響起惠桃,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤浦夷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后刽射,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡剃执,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年誓禁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肾档。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡摹恰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出怒见,到底是詐尸還是另有隱情俗慈,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布遣耍,位于F島的核電站闺阱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏舵变。R本人自食惡果不足惜酣溃,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纪隙。 院中可真熱鬧赊豌,春花似錦、人聲如沸绵咱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悲伶。三九已至艾恼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間麸锉,已是汗流浹背蒂萎。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留淮椰,地道東北人五慈。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓纳寂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親泻拦。 傳聞我的和親對象是個殘疾皇子毙芜,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容