23 标签联合

  • 这里开发一个类似于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为首部和尾部来实现
template<typename... Types>
union VariantStorage;

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

template<>
union VariantStorage<> {};
  • 这里的union保证足够的大小和对齐来存储参数列表的任意类型,但这个union本身很难使用,因为大多用来实现Variant的技术将使用继承,而这对union是不允许的。取代做法是用最底层的字符数组来存储,这个数组足够大到存储任何类型(通过一个计算最大类型的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时字符数组上所需的核心操作
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);
};
  • 元函数FindIndexOfT用来找出Types中类型T的位置,实现如下
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不包含值的情况,这是一种奇态(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()成员函数定义如下,用于确定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
  • 如果查找的类型未在列表中在找到,FindIndexOfT将不会包含一个value成员,于是VariantChoice基类将实例化失败,从而造成is()的编译错误,这样就可以防止用户请求无法存储的类型

提取

  • get()成员函数通过接受一个类型,提取一个该类型存储值的引用,并且仅当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构造函数实现,这个构造函数接受一个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的构造函数引入到Variant中
using VariantChoice<Types, Types...>::VariantChoice...;
  • 这个using声明为Types中的每个类型生成一个Variant的构造函数
// Variant<int, double, string>的构造函数实际是
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)
  {
    // 如果匹配则调用placement delete
    getDerived().template getBufferAs<T>()->~T();
    return true;
  }
  return false;
}
  • 只有当discriminator匹配时,VariantChoice::destroy()操作是有用的。但通常希望不考虑当前活动的类型,直接销毁存储在Variant中的值,因此Variant::destroy()将调用基类中所有的VariantChoice::destroy()
// variant/variantdestroy.hpp

template<typename... Types>
void Variant<Types...>::destroy()
{
  // 调用所有的VariantChoice::destroy(),至多一个成功
  bool results[] = { VariantChoice<Types, Types...>::destroy()...};
  this->setDiscriminator(0); // 表示不再存储一个值,Variant为空
}
  • 数组results的作用是提供一个能使用初始化列表的上下文,C++17中可以使用折叠表达式来消除对数组results的需要
// variant/variantdestroy17.hpp

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

赋值

  • 对于同类型直接赋值,对于不同类型则需要析构现有值,再用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()析构现有值
    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
  • 自赋值发生情况如下。如果使用两步赋值,源值将在拷贝前会被析构,导致内存崩溃。但好在自赋值意味着discriminator匹配,所以会使用同类型赋值,因此不会出问题
v = v.get<T>()
  • 如果现有值的销毁已经完成,但是新值的初始化抛出异常,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()成员函数用于检查Variant是否为空
// variant/variantempty.hpp

template<typename... Types>
bool Variant<Types...>::empty() const
{
  return this->getDiscriminator() == 0;
}
  • 第三个问题是标准化委员会在C++ 17标准化过程结束时才意识到的一个微妙问题。编译器通常希望产生高性能代码,而提高生成代码性能的主要机制是避免从内存到寄存器的重复的数据复制。为此编译器必须做一些假设,其中一种假设是某些类型的数据在其生命周期内是不可变的,这包括const数据、引用(可以初始化,但之后不修改)以及存储在多态对象中的一些bookkeeping data(用于调度虚拟函数、定位虚拟基类、以及处理typeid和dynamic_cast操作符)
  • 上面两步分配过程的问题是,它以一种编译器可能无法识别的方式悄悄地结束一个对象的生命周期,并在同一位置开始另一个对象的生命周期。因此,编译器可能假定它从Variant对象的先前状态获取的值仍然有效,而实际上placement new的初始化会使其无效,这就导致编译具有不可变数据成员的Variant时偶尔会产生无效结果。这种bug通常很难追踪(一部分原因是它们很少发生,另一部分原因是它们在源代码中并不真正可见)
  • 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>();
}
  • 为此需要引入一个函数模板
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,它接受一个仿函数或者lambda,接受的参数就相当于访问者的角色
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()的实现直接委托给variantVisitImpl()即可,传递Variant本身,转发访问者,并提供完整的类型列表。三种实现分别在this对象作为Variant&、const Variant&或Variant&&传递时被调用,类成员函数后加引用限定符的用法可参考《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...>());
}
  • 这里还需要实现VisitResult元函数。visit()的结果类型仍不能确定,比如接受如下lambda,其返回类型取决于输入的参数类型
[] (const auto& value) { return value + 1; }
  • visit()设置一个模板参数R,其默认实参为ComputedResultType
template<typename R = ComputedResultType, typename Visitor>
VisitResult<R, Visitor, Types&...> visit(Visitor&& vis) &;
  • 现在希望显式指定返回结果的类型时,将R设为返回类型
// visit的返回类型是Variant<int, double>中的类型
v.visit<Variant<int, double>>([] (const auto& value) { return value + 1; });
  • 不显式指定时,将使用R的默认实参ComputedResultType(一个只需声明无需实现的类,只是用作标签)
class ComputedResultType;
  • 元函数VisitResult的实现如下
// 显式指定返回类型的情形
template<typename R, typename Visitor, typename... ElementTypes>
class VisitResultT {
 public:
  using Type = R;
};

// 对使用默认实参ComputedResultType的情形特化一个版本
template<typename Visitor, typename… ElementTypes>
class VisitResultT<ComputedResultType, Visitor, ElementTypes…> {
  ... // 实现见后
}

template<typename R, typename Visitor, typename... ElementTypes>
using VisitResult = typename VisitResultT<R, Visitor, ElementTypes...>::Type;
  • 对于通用类型(common type),C++已经有了一个合理的概念:在三元表达式b ? x : y中,结果类型是x和y之间的通用类型,例如x为int,y为double,则通用类型为double。由此实现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的每个类型调用访问者时生成的结果类型的通用类型,实现如下
// 对T类型值调用访问者时生成的结果类型
template<typename Visitor, typename T>
using VisitElementResult = decltype(declval<Visitor>()(declval<T>()));

// 对每个元素类型调用访问者时的通用结果类型
template<typename Visitor, typename... ElementTypes>
class VisitResultT<ComputedResultType, Visitor, ElementTypes...> {
  using ResultTypes = // 每个元素调用访问者时生成的结果类型
    Typelist<VisitElementResult<Visitor, ElementTypes>...>;
 public:
  using Type = // 所有结果类型的通用类型
    Accumulate<PopFront<ResultTypes>, CommonTypeT, Front<ResultTypes>>;
};
  • 标准库提供了std::common_type,它组合了CommonTypeT和Accumulate以对任意数量类型生成通用类型,因此特化的VisitResultT可以更简单地实现如下
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的异常状态提升到了通用状态
  • 一个避免引入空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需要确定它当前存储的类型,将该值拷贝构造到字符数组中,并设置discriminator,这两点在VariantChoice的拷贝赋值运算符中已提供,通过对要拷贝的Variant使用visit()即可简洁紧凑地实现目的
// 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的实现也适用于模板化形式的拷贝和移动构造
// 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;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容