C++三剑客之std::variant(二):深入剖析

目录

1.概述

2.辅助类介绍

2.1.std::negation

2.2.std::conjunction

2.3.std::is_destructible

2.4.std::is_object

2.5.is_default_constructible

2.6.std::is_trivially_destructible

2.7.std::in_place_type和std::in_place_index

3.原理分析

3.1.存储分析

3.2.构造函数

3.2.1.默认构造

3.2.2.使用单一值初始化

3.2.3.std::in_place_type

3.2.4.std::in_place_index

3.3.访问值

3.3.1.直接赋值

3.3.2.emplace

3.3.3.get

3.3.4.get_if

4.总结


前一篇关于std::variant的博客详细介绍了std::variant的使用和一些注意事项,熟悉和了解它的使用方法后,我们接着追根溯源,探索其本质,仔细阅读它的实现,分析一下源码。

1.概述

本文我们主要研究问题模板类std::variant如何做到存任意多个类型值的容器?不同类型怎么做到巧妙的构造与转换的?多种构造函数如何实现?内部数据怎么储存?为什么不能保存引用、数组和void类型?

std::variant是在头文件variant中,是C++17引入的,本文以VS2019平台展开讲解variant的原理和深层次用法。

2.辅助类介绍

2.1.std::negation

std::negation 逻辑非元函数,一元函数对象类,其调用将返回对其实参求反的结果(由一元操作符-返回)。如:

#include <iostream>
#include <type_traits>
 
static_assert(
    std::is_same<
        std::bool_constant<false>,
        typename std::negation<std::bool_constant<true>>::type>::value,
    "");
static_assert(
    std::is_same<
        std::bool_constant<true>,
        typename std::negation<std::bool_constant<false>>::type>::value,
    "");
 
int main()
{
    std::cout << std::negation<std::bool_constant<true>>::value << '\n';  //输出:false
    std::cout << std::negation<std::bool_constant<false>>::value << '\n'; //输出:true
}

std::bool_constant<true>即是 true_type,取值为true,逻辑反则为false。std::bool_constant<false> 即是false_type,取值为false,逻辑反则为true。

2.2.std::conjunction

std::conjunction 逻辑与元对象,在头文件type_traits中,一般用在判断可变参数是否满足某种条件上。示例如下:

#include <iostream>
#include <type_traits>
 
// func is enabled if all Ts... have the same type as T
template<typename T, typename... Ts>
std::enable_if_t<std::conjunction_v<std::is_same<T, Ts>...>>
func(T, Ts...) {
    std::cout << "all types in pack are T\n";
}
 
// otherwise
template<typename T, typename... Ts>
std::enable_if_t<!std::conjunction_v<std::is_same<T, Ts>...>>
func(T, Ts...) {
    std::cout << "not all types in pack are T\n";
}
 
int main() {
    func(1, 2, 3);
    func(1, 2, "hello!");
}

输出:

all types in pack are T
not all types in pack are T

上述代码在func中用std::is_same判断模板函数的参数类型是否都是一样的,所有参数类型一样判定为true,否则为false;同样std::variant的源码也用到了这个,如:

template <class... _Types>
using _Variant_storage = _Variant_storage_<conjunction_v<is_trivially_destructible<_Types>...>, _Types...>;

std::conjunction就是判断可变参数对象是否都为简单销毁对象。

2.3.std::is_destructible

std::is_destructible 类型特征来检查一个类是否有可析构的类型。这有助于我们在编译时发现潜在的问题,例如试图删除非指针类型的对象。但它并不保证这个类型的析构函数是否真正做了正确的清理工作。因此,在定义类的析构函数时,我们需要仔细地考虑它是否真正释放了所有分配的资源。如下示例:

#include <iostream>
#include <fstream>
#include <type_traits>

class MyClass {
public:
  MyClass(int size) : arr(new int[size]), file("example.txt") {}
  ~MyClass() { delete [] arr; }

private:
  int* arr;
  std::ofstream file;
};

int main() {
  std::cout << std::is_destructible<MyClass>::value << '\n'; //输出:true
  std::cout << std::is_destructible<int>::value << '\n';     //输出: true
  std::cout << std::is_destructible<int[]>::value << '\n';   //输出:false
  std::cout << std::is_destructible<std::ofstream>::value << '\n'; //输出:true
}

从上面的代码可以看出,我们定义的MyClass类具有可析构的类型。而int类型和std::ofstream类型也是可析构的。但是,int[]类型不是可析构的。这是因为数组类型不支持默认构造函数、拷贝构造函数或移动构造函数,从而导致不能正确地销毁。

2.4.std::is_object

std::is_object是一个用于元编程的C++类型特性,用于判断一个类型是否是对象类型,而不是类类型或枚举类型。这个在我之前的博客也讲的很清楚,如果还不是特别明白,可以再去翻翻博客C++之std::is_object-CSDN博客;在这里我就不多赘述了。

2.5.is_default_constructible

std::is_default_constructible模板,用于判断一个类型是否有默认构造函数。因为在某些情况下,需要在编译期间确定一个类型是否有默认构造函数。在使用该模板时需要包含头文件type_traits。示例代码:

#include <iostream>
#include <type_traits>

class X {
public:
    X(int x): m_x(x) { }
private:
    int m_x;
};

class Y {
public:
    Y() = default;
private:
    double m_y;
};

int main() {
    std::cout << std::is_default_constructible<X>::value << '\n'; //输出:false
    std::cout << std::is_default_constructible<Y>::value << '\n'; //输出:true
    std::cout << std::is_default_constructible<int>::value << '\n'; //输出:true
    std::cout << std::is_default_constructible<int[]>::value << '\n'; //输出:false
    return 0;
}

在上述示例代码中,我们定义了两个类X和Y,分别设置了构造函数和默认构造函数。然后分别使用is_default_constructible模板来判断是否有默认构造函数,最后还演示了一些基本类型和数组类型的情况。

2.6.std::is_trivially_destructible

判断一个类型T是否是一个平凡的可销毁类型(trivivally destructible)。主要用于检查这个类型的析构函数。一个trivivally destructible类(由class,struct/union)需要满足下面的条件:使用默认的析构函数、析构函数不能为虚的、它的基类和静态成员类型也必须是一个trivivally destructible类。如下示例:

// is_trivially_destructible example
#include <iostream>
#include <type_traits>

struct A { }; /* 符合trivivally destructible类型定义 */
struct B { ~B(){} }; /* 没有使用隐式应答的析构函数, 即编译器合成的默认析构函数, 因此不是trivivally destructible类型 */

int main() {
  std::cout << std::boolalpha; /* 将输出流bool解析为true/false, 而不是1/0 */
  std::cout << "is_trivially_destructible:" << std::endl;
  std::cout << "int: " << std::is_trivially_destructible<int>::value << std::endl; /* 基本类型是trivivally destructible类型 */
  std::cout << "A: " << std::is_trivially_destructible<A>::value << std::endl; /* A是trivivally destructible类型 */
  std::cout << "B: " << std::is_trivially_destructible<B>::value << std::endl; /* B不是trivivally destructible类型 */
  return 0;
}

输出:

is_trivially_destructible:
int: true
A: true
B: false

2.7.std::in_place_type和std::in_place_index

std::in_place_inde实际就是一个占位符,它的定义如下:

template <size_t _Idx>
inline constexpr in_place_index_t<_Idx> in_place_index{};

   in_place_index_t 定义如下:

template <size_t>
struct in_place_index_t { // tag that selects the index of a type to construct in place
    explicit in_place_index_t() = default;
};

从上面的代码可以看出 std::in_place_inde<_Idx> 是用来标识参数位置的数据类型,不过它是根据参数位置序号来判断的;同理也可以分析出std::in_place_type<_Ty>也是用来标识参数位置的数据类型,不过它是根据参数的类型来判断的,从std::in_place_type的定义可以看出来:

struct in_place_t { // tag used to select a constructor which initializes a contained object in place
    explicit in_place_t() = default;
};
inline constexpr in_place_t in_place{};

template <class>
struct in_place_type_t { // tag that selects a type to construct in place
    explicit in_place_type_t() = default;
};
template <class _Ty>
inline constexpr in_place_type_t<_Ty> in_place_type{};

3.原理分析

3.1.存储分析

std::variant的内部用了union递归存储各种类型的数据,在头文件variant中按码索骥找到了存储std::variant的类_Variant_storage,内部定义了一个union:

template <bool _TrivialDestruction, class... _Types>
class _Variant_storage_ {}; // empty storage (empty "_Types" case)

// ALIAS TEMPLATE _Variant_storage
template <class... _Types>
using _Variant_storage = _Variant_storage_<conjunction_v<is_trivially_destructible<_Types>...>, _Types...>;

template <class _First, class... _Rest>
class _Variant_storage_<true, _First, _Rest...> { // Storage for variant alternatives (trivially destructible case)
public:
    static constexpr size_t _Size = 1 + sizeof...(_Rest);
    union {
        remove_const_t<_First> _Head;
        _Variant_storage<_Rest...> _Tail;
    };

    _Variant_storage_() noexcept {} // no initialization (no active member)

    ...
};

union自动按最大数据类型对齐的。std::variant的内存布局为:

第N个的_Tail为 _Variant_storage_<true> 或  _Variant_storage_<false>,举个例子,如定义

std::variant<int, double, bool, float> y; 那么y的内存布局如下所示:

_Variant_storage_根据对象是否为"简单销毁对象"划分为:

_Variant_storage_<true, _Types...> 和 _Variant_storage_<false, _Types...>,_Variant_storage_<true, _Types...>的实现为:

template <class... _Types>
using _Variant_storage = _Variant_storage_<conjunction_v<is_trivially_destructible<_Types>...>, _Types...>;

template <class _First, class... _Rest>
class _Variant_storage_<true, _First, _Rest...> { // Storage for variant alternatives (trivially destructible case)
public:
    static constexpr size_t _Size = 1 + sizeof...(_Rest);
    union {
        remove_const_t<_First> _Head;
        _Variant_storage<_Rest...> _Tail;
    };

    _Variant_storage_() noexcept {} // no initialization (no active member)

    template <class... _Types>
    constexpr explicit _Variant_storage_(integral_constant<size_t, 0>, _Types&&... _Args) noexcept(
        is_nothrow_constructible_v<_First, _Types...>)
        : _Head(static_cast<_Types&&>(_Args)...) {} // initialize _Head with _Args...

    template <size_t _Idx, class... _Types, enable_if_t<(_Idx > 0), int> = 0>
    constexpr explicit _Variant_storage_(integral_constant<size_t, _Idx>, _Types&&... _Args) noexcept(
        is_nothrow_constructible_v<_Variant_storage<_Rest...>, integral_constant<size_t, _Idx - 1>, _Types...>)
        : _Tail(integral_constant<size_t, _Idx - 1>{}, static_cast<_Types&&>(_Args)...) {} // initialize _Tail (recurse)

    _NODISCARD constexpr _First& _Get() & noexcept {
        return _Head;
    }
    _NODISCARD constexpr const _First& _Get() const& noexcept {
        return _Head;
    }
    _NODISCARD constexpr _First&& _Get() && noexcept {
        return _STD move(_Head);
    }
    _NODISCARD constexpr const _First&& _Get() const&& noexcept {
        return _STD move(_Head);
    }
};

_Variant_storage_<false, _Types...>的实现为:

template <class _First, class... _Rest>
class _Variant_storage_<false, _First, _Rest...> { // Storage for variant alternatives (non-trivially destructible case)
public:
    static constexpr size_t _Size = 1 + sizeof...(_Rest);
    union {
        remove_const_t<_First> _Head;
        _Variant_storage<_Rest...> _Tail;
    };

    ~_Variant_storage_() noexcept { // explicitly non-trivial destructor (which would otherwise be defined as deleted
                                    // since the class has a variant member with a non-trivial destructor)
    }

    _Variant_storage_() noexcept {} // no initialization (no active member)

    template <class... _Types>
    constexpr explicit _Variant_storage_(integral_constant<size_t, 0>, _Types&&... _Args) noexcept(
        is_nothrow_constructible_v<_First, _Types...>)
        : _Head(static_cast<_Types&&>(_Args)...) {} // initialize _Head with _Args...

    template <size_t _Idx, class... _Types, enable_if_t<(_Idx > 0), int> = 0>
    constexpr explicit _Variant_storage_(integral_constant<size_t, _Idx>, _Types&&... _Args) noexcept(
        is_nothrow_constructible_v<_Variant_storage<_Rest...>, integral_constant<size_t, _Idx - 1>, _Types...>)
        : _Tail(integral_constant<size_t, _Idx - 1>{}, static_cast<_Types&&>(_Args)...) {} // initialize _Tail (recurse)

    _Variant_storage_(_Variant_storage_&&)      = default;
    _Variant_storage_(const _Variant_storage_&) = default;
    _Variant_storage_& operator=(_Variant_storage_&&) = default;
    _Variant_storage_& operator=(const _Variant_storage_&) = default;

    _NODISCARD constexpr _First& _Get() & noexcept {
        return _Head;
    }
    _NODISCARD constexpr const _First& _Get() const& noexcept {
        return _Head;
    }
    _NODISCARD constexpr _First&& _Get() && noexcept {
        return _STD move(_Head);
    }
    _NODISCARD constexpr const _First&& _Get() const&& noexcept {
        return _STD move(_Head);
    }
};

_Variant_storage_类中提供了对外访问对象的接口 _Get(),包括左值引用和右值引用。由于上层的类_Variant_base是private继承_Variant_storage_的,从下面的代码可以看出:

template <class... _Types>
class _Variant_base
    : private _Variant_storage<_Types...> { // Associate an integral discriminator with a _Variant_storage
public:
    using _Index_t                       = _Variant_index_t<sizeof...(_Types)>;
    static constexpr auto _Invalid_index = static_cast<_Index_t>(-1);
    _Index_t _Which;

    using _Storage_t = _Variant_storage<_Types...>;
    _NODISCARD constexpr _Storage_t& _Storage() & noexcept { // access this variant's storage
        return *this;
    }
    _NODISCARD constexpr const _Storage_t& _Storage() const& noexcept { // access this variant's storage
        return *this;
    }
    _NODISCARD constexpr _Storage_t&& _Storage() && noexcept { // access this variant's storage
        return _STD move(*this);
    }
    _NODISCARD constexpr const _Storage_t&& _Storage() const&& noexcept { // access this variant's storage
        return _STD move(*this);
    }

    _Variant_base() noexcept : _Storage_t{}, _Which{_Invalid_index} {} // initialize to the value-less state
    ...
};

由于上层的类不能访问_Variant_storage_的成员变量和函数,所以提供了专门的访问数据接口_Variant_raw_get,代码如下:

template <size_t _Idx, class _Storage>
_NODISCARD constexpr decltype(auto) _Variant_raw_get(
    _Storage&& _Obj) noexcept { // access the _Idx-th element of a _Variant_storage
    if constexpr (_Idx == 0) {
        return static_cast<_Storage&&>(_Obj)._Get();
    } else if constexpr (_Idx == 1) {
        return static_cast<_Storage&&>(_Obj)._Tail._Get();
    } else if constexpr (_Idx == 2) {
        return static_cast<_Storage&&>(_Obj)._Tail._Tail._Get();
    } else if constexpr (_Idx == 3) {
        return static_cast<_Storage&&>(_Obj)._Tail._Tail._Tail._Get();
    } else if constexpr (_Idx == 4) {
        return static_cast<_Storage&&>(_Obj)._Tail._Tail._Tail._Tail._Get();
    } else if constexpr (_Idx == 5) {
        return static_cast<_Storage&&>(_Obj)._Tail._Tail._Tail._Tail._Tail._Get();
    } else if constexpr (_Idx == 6) {
        return static_cast<_Storage&&>(_Obj)._Tail._Tail._Tail._Tail._Tail._Tail._Get();
    } else if constexpr (_Idx == 7) {
        return static_cast<_Storage&&>(_Obj)._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Get();
    } else if constexpr (_Idx < 16) {
        return _Variant_raw_get<_Idx - 8>(
            static_cast<_Storage&&>(_Obj)._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail);
    } else if constexpr (_Idx < 32) {
        return _Variant_raw_get<_Idx - 16>(
            static_cast<_Storage&&>(_Obj)
                ._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail);
    } else if constexpr (_Idx < 64) {
        return _Variant_raw_get<_Idx - 32>(
            static_cast<_Storage&&>(_Obj)
                ._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail
                ._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail);
    } else { // _Idx >= 64
        return _Variant_raw_get<_Idx - 64>(
            static_cast<_Storage&&>(_Obj)
                ._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail
                ._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail
                ._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail
                ._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail._Tail);
    }
}

函数中参数序号低于8的直接访问值,大于8的递归调用自身来访问值。

3.2.构造函数

3.2.1.默认构造

默认构造函数如下:

template <class _First = _Meta_front<variant>, enable_if_t<is_default_constructible_v<_First>, int> = 0>
    constexpr variant() noexcept(is_nothrow_default_constructible_v<_First>)
        : _Mybase(in_place_index<0>) {} // value-initialize alternative 0

取出第一个参数_Meta_front,调用基类的构造函数,生成对象。象如下定义std::variant就会调用此构造函数:

std::variant<int, double, bool, float> y;

3.2.2.使用单一值初始化

  示例如下:

std::variant<bool, int, std::string> v(25);

如果这样编码,就会直接调用std::variant的单一赋值的构造函数,源码如下:

template <class _Ty,
        enable_if_t<sizeof...(_Types) != 0 //
                        && !is_same_v<_Remove_cvref_t<_Ty>, variant> //
                        && !_Is_specialization_v<_Remove_cvref_t<_Ty>, in_place_type_t> //
                        && !_Is_in_place_index_specialization<_Remove_cvref_t<_Ty>> //
                        && is_constructible_v<_Variant_init_type<_Ty, _Types...>, _Ty>, //
            int> = 0>
constexpr variant(_Ty&& _Obj) noexcept(is_nothrow_constructible_v<_Variant_init_type<_Ty, _Types...>, _Ty>)
        : _Mybase(in_place_index<_Variant_init_index<_Ty, _Types...>::value>, static_cast<_Ty&&>(_Obj)) {
        // initialize to the type selected by passing _Obj to the overload set f(Types)...
    }

通过_Variant_init_index找到_Ty在_Types...的位置,然后再调用_Variant_base的构造函数:

template <size_t _Idx, class... _UTypes,
        enable_if_t<is_constructible_v<_Meta_at_c<variant<_Types...>, _Idx>, _UTypes...>, int> = 0>
    constexpr explicit _Variant_base(in_place_index_t<_Idx>, _UTypes&&... _Args) noexcept(
        is_nothrow_constructible_v<_Meta_at_c<variant<_Types...>, _Idx>, _UTypes...>)
        : _Storage_t(integral_constant<size_t, _Idx>{}, static_cast<_UTypes&&>(_Args)...),
          _Which{static_cast<_Index_t>(_Idx)} { // initialize alternative _Idx from _Args...
    }

初始化_Which和_Variant_storage,如果有多个可能的类型匹配,可能导致歧义。

3.2.3.std::in_place_type

示例如下:

std::variant<int, double, std::string> v(std::in_place_type<double>, 34.66);

如果这样编码,就会直接调用std::variant的std::in_place_type构造函数,源码如下:

template <class _Ty, class... _UTypes, class _Idx = _Meta_find_unique_index<variant, _Ty>,
        enable_if_t<_Idx::value != _Meta_npos && is_constructible_v<_Ty, _UTypes...>, int> = 0>
    constexpr explicit variant(in_place_type_t<_Ty>, _UTypes&&... _Args) noexcept(
        is_nothrow_constructible_v<_Ty, _UTypes...>) // strengthened
        : _Mybase(in_place_index<_Idx::value>, static_cast<_UTypes&&>(_Args)...) {
        // initialize alternative _Ty from _Args...
    }

通过_Meta_find_unique_index找到_Ty在_Types...的位置,_Meta_find_unique_index循环递归查找_Ty的详细实现:

template <class _List, class _Ty>
struct _Meta_find_unique_index_ {
    using type = integral_constant<size_t, _Meta_npos>;
};
template <class _List, class _Ty>
using _Meta_find_unique_index =
    // The index of _Ty in _List if it occurs exactly once, otherwise _Meta_npos
    typename _Meta_find_unique_index_<_List, _Ty>::type;

constexpr size_t _Meta_find_unique_index_i_2(const bool* const _Ptr, const size_t _Count,
    const size_t
        _First) { // return _First if there is no _First < j < _Count such that _Ptr[j] is true, otherwise _Meta_npos
    return _First != _Meta_npos && _Meta_find_index_i_(_Ptr, _Count, _First + 1) == _Meta_npos ? _First : _Meta_npos;
}

constexpr size_t _Meta_find_unique_index_i_(const bool* const _Ptr,
    const size_t _Count) { // Pass the smallest i such that _Ptr[i] is true to _Meta_find_unique_index_i_2
    return _Meta_find_unique_index_i_2(_Ptr, _Count, _Meta_find_index_i_(_Ptr, _Count));
}

template <template <class...> class _List, class _First, class... _Rest, class _Ty>
struct _Meta_find_unique_index_<_List<_First, _Rest...>, _Ty> {
    using type = integral_constant<size_t,
        _Meta_find_unique_index_i_(_Meta_find_index_<_List<_First, _Rest...>, _Ty>::_Bools, 1 + sizeof...(_Rest))>;
};

最后调用_Variant_base的构造函数,生成_Variant_storage,存储数据。

3.2.4.std::in_place_index

示例如下:

std::variant<bool, std::string> v(std::in_place_index<1>, "14256435");

如果这样编码,就会直接调用std::variant的std::in_place_index构造函数,源码如下:

template <size_t _Idx, class... _UTypes,
        enable_if_t<is_constructible_v<_Meta_at_c<variant, _Idx>, _UTypes...>, int> = 0>
    constexpr explicit variant(in_place_index_t<_Idx>, _UTypes&&... _Args) noexcept(
        is_nothrow_constructible_v<_Meta_at_c<variant, _Idx>, _UTypes...>) // strengthened
        : _Mybase(in_place_index<_Idx>, static_cast<_UTypes&&>(_Args)...) {
        // initialize alternative _Idx from _Args...
    }

直接调用_Variant_base的构造函数,这种生成std::vaiant流程会简单一些,也比较好理解一些。

3.3.访问值

3.3.1.直接赋值

如:

std::variant<bool, int,std::string> v;
v = "hello world"; 

如果这样编码,就会直接调用std::variant的operator=,源码如下:

// assignment [variant.assign]
    template <class _Ty, enable_if_t<!is_same_v<_Remove_cvref_t<_Ty>, variant> //
                                         && is_constructible_v<_Variant_init_type<_Ty, _Types...>, _Ty> //
                                         && is_assignable_v<_Variant_init_type<_Ty, _Types...>&, _Ty>, //
                             int> = 0>
    variant& operator=(_Ty&& _Obj) noexcept(is_nothrow_assignable_v<_Variant_init_type<_Ty, _Types...>&, _Ty>&&
            is_nothrow_constructible_v<_Variant_init_type<_Ty, _Types...>, _Ty>) {
        // assign/emplace the alternative chosen by overload resolution of _Obj with f(_Types)...
        constexpr size_t _TargetIdx = _Variant_init_index<_Ty, _Types...>::value;
        if (index() == _TargetIdx) {
            auto& _Target = _Variant_raw_get<_TargetIdx>(_Storage());
            _Target       = static_cast<_Ty&&>(_Obj);
        } else {
            using _TargetTy = _Variant_init_type<_Ty, _Types...>;
            if constexpr (_Variant_should_directly_construct_v<_TargetTy, _Ty>) {
                this->_Reset();
                _Emplace_valueless<_TargetIdx>(static_cast<_Ty&&>(_Obj));
            } else {
                _TargetTy _Temp(static_cast<_Ty&&>(_Obj));
                this->_Reset();
                _Emplace_valueless<_TargetIdx>(_STD move(_Temp));
            }
        }

        return *this;
    }

它的流程如下:

关键步骤:1)比较当前的index()和_TargetIdx是否相同,相同,直接赋值。

2)不相同,这需要析构原来的对象,重新构造新的对象,赋值等操作,流程会比较复杂一些,这些实现是在_Emplace_valueless里面,代码如下:

template <size_t _Idx, class... _ArgTypes>
    _Meta_at_c<variant, _Idx>& _Emplace_valueless(_ArgTypes&&... _Args) noexcept(
        is_nothrow_constructible_v<_Meta_at_c<variant, _Idx>, _ArgTypes...>) {
        // initialize alternative _Idx from _Args...
        // pre: valueless_by_exception()
        auto& _Obj = _Variant_raw_get<_Idx>(_Storage());
        _Construct_in_place(_Obj, static_cast<_ArgTypes&&>(_Args)...);
        this->_Set_index(_Idx);
        return _Obj;
    }

3.3.2.emplace

从emplace的源代码

template <class _Ty, class... _ArgTypes, size_t _Idx = _Meta_find_unique_index<variant, _Ty>::value,
        enable_if_t<_Idx != _Meta_npos && is_constructible_v<_Ty, _ArgTypes...>, int> = 0>
    _Ty& emplace(_ArgTypes&&... _Args) noexcept(is_nothrow_constructible_v<_Ty, _ArgTypes...>) /* strengthened */ {
        // emplace alternative _Ty from _Args...
        this->_Reset();
        return _Emplace_valueless<_Idx>(static_cast<_ArgTypes&&>(_Args)...);
    }

可以看出跟3.3.1的流程差不多,这里就不多赘述了。

3.3.3.get

1) 通过序号 index 来get值

template <size_t _Idx, class... _Types>
_NODISCARD constexpr decltype(auto) get(
    variant<_Types...>& _Var) { // access the contained value of _Var if its _Idx-th alternative is active
    static_assert(_Idx < sizeof...(_Types), "variant index out of bounds");
    if (_Var.index() == _Idx) {
        return _Variant_raw_get<_Idx>(_Var._Storage());
    }

    _Throw_bad_variant_access();
}

 通过当前的index()和_Idx比对,来获取std::variant的值,_Variant_raw_get函数在3.1章节讲过;

如果当前的index()不是_Idx,这会抛出异常。

2)通过 类型_Ty 来get值

template <class _Ty, class... _Types>
_NODISCARD constexpr decltype(auto) get(
    variant<_Types...>& _Var) { // access the contained value of _Var if its alternative _Ty is active
    constexpr size_t _Idx = _Meta_find_unique_index<variant<_Types...>, _Ty>::value;
    static_assert(_Idx < sizeof...(_Types),
        "get<T>(variant<Types...>&) requires T to occur exactly once in Types. (N4835 [variant.get]/5)");
    return _STD get<_Idx>(_Var);
}

通过_Meta_find_unique_index获取到类型_Ty的_Idx, 然后调用序号index版本的get来获取值。

3.3.4.get_if

get_if也是有两种,通过序号index和类型_Ty来获取值,从源码的

template <size_t _Idx, class... _Types>
_NODISCARD constexpr auto get_if(
    variant<_Types...>* _Ptr) noexcept { // get the address of *_Ptr's contained value if it holds alternative _Idx
    static_assert(_Idx < sizeof...(_Types), "variant index out of bounds");
    return _Ptr && _Ptr->index() == _Idx ? _STD addressof(_Variant_raw_get<_Idx>(_Ptr->_Storage())) : nullptr;
}

template <class _Ty, class... _Types>
_NODISCARD constexpr add_pointer_t<_Ty> get_if(
    variant<_Types...>* _Ptr) noexcept { // get the address of *_Ptr's contained value if it holds alternative _Ty
    constexpr size_t _Idx = _Meta_find_unique_index<variant<_Types...>, _Ty>::value;
    static_assert(_Idx != _Meta_npos,
        "get_if<T>(variant<Types...> *) requires T to occur exactly once in Types. (N4835 [variant.get]/9)");
    return _STD get_if<_Idx>(_Ptr);
}

可以看到,基本上可以3.3.3的get原理差不多,在这里就不多赘述了。

4.总结

到此我们已经全部分析完毕,细节也谈及了,喜欢的给个赞并收藏,谢谢。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/330381.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【昕宝爸爸小模块】深入浅出之针对大Excel做文件读取问题

➡️博客首页 https://blog.csdn.net/Java_Yangxiaoyuan 欢迎优秀的你&#x1f44d;点赞、&#x1f5c2;️收藏、加❤️关注哦。 本文章CSDN首发&#xff0c;欢迎转载&#xff0c;要注明出处哦&#xff01; 先感谢优秀的你能认真的看完本文&…

VUE 中的 v-for 和 v-if 是否可以共存

VUE 中的 v-for 和 v-if 是否可以共存 前言1、面试经2、正确回答3、总结总结&#xff1a; 前言 要成功&#xff0c;先发疯&#xff0c;头脑简单往前冲&#xff01; 三金四银&#xff0c;金九银十&#xff0c;多学知识&#xff0c;也不能埋头苦干&#xff0c;要成功&#xff0c…

uniapp中uview组件库的NoticeBar 滚动通知 使用方法

目录 #平台差异说明 #基本使用 #配置主题 #配置图标 #配置滚动速度 #控制滚动的开始和暂停 #事件回调 #API #Props #Events 该组件用于滚动通告场景&#xff0c;有多种模式可供选择 #平台差异说明 AppH5微信小程序支付宝小程序百度小程序头条小程序QQ小程序√√√√…

2018年认证杯SPSSPRO杯数学建模B题(第一阶段)动态模糊图像全过程文档及程序

2018年认证杯SPSSPRO杯数学建模 B题 动态模糊图像 原题再现&#xff1a; 人眼由于存在视觉暂留效应&#xff0c;所以看运动的物体时&#xff0c;看到的每一帧画面都包含了一段时间内 (大约 1/24 秒) 的运动过程&#xff0c;所以这帧画面事实上是模糊的。对电影的截图来说&…

量化研究员!你应该如何写一手好代码

即使是Quant Researcher&#xff0c; 写一手高质量的代码也是非常重要的。再好的思路&#xff0c;如果不能正确地实现&#xff0c;都是没有意义的。 写一手高质量的代码的意义&#xff0c;对Quant developer来讲就更是自不待言了。这篇笔记就介绍一些python best practice。 始…

Unity Shader 的模板测试效果

模板测试是渲染管线中逐片元操作的一环&#xff0c;它的作用是筛选出指定模板的片元&#xff0c;而不符合模板的片元会被舍弃&#xff0c;从而做到一个遮罩的效果。 以下是Unity中实践的一个效果&#xff1a; 场景中可以看出&#xff0c;熊模型和茶壶模型都在差不多的位置&am…

idea社区版 MybatisCodeHelperPro插件使用介绍

文章目录 一、插件介绍二、idea社区版安装MybatisCodeHelperPro插件三、问题记录1. DatabaseHelper插件 加载不了部分数据库链接的列信息2. DatabaseHelper插件 数据库列显示顺序错乱3. MybatisCodeHelperPro插件 数据库字段不提示4. MybatisCodeHelperPro插件 特殊字段增加反引…

SpringBoot 统计API接口用时该使用过滤器还是拦截器?

统计请求的处理时间&#xff08;用时&#xff09;既可以使用 Servlet 过滤器&#xff08;Filter&#xff09;&#xff0c;也可以使用 Spring 拦截器&#xff08;Interceptor&#xff09;。两者都可以在请求处理前后插入自定义逻辑&#xff0c;从而实现对请求响应时间的统计。 …

汽车芯片「新变量」

编者按&#xff1a;汽车行业的格局重构和技术革新&#xff0c;也在推动芯片赛道进入变革周期。不同商业模式的博弈&#xff0c;持续升温。 对于智能汽车来说&#xff0c;过去几年经历了多轮硬件和软件的性能迭代&#xff0c;甚至是革新&#xff0c;如今&#xff0c;市场正在进…

张驰咨询:六西格玛工具企业如何实现资源优化与效率最大化

在这个百年未有之大变局的时期&#xff0c;我们面临着前所未有的挑战与机遇。我一直在寻找提升效率&#xff0c;减少资源浪费的方法。而六西格玛培训提供了一个系统化的解决方案&#xff0c;它不仅是一套工具&#xff0c;更是一种精益思维。 让我们一起思考一个问题&#xff1a…

VMware workstation安装SUSE Linux Enterprise Server 12 SP5虚拟机并配置网络

VMware workstation安装SUSE Linux Enterprise Server 12 SP5虚拟机并配置网络 SUSE Linux Enterprise Server是企业级Linux系统&#xff0c;适合企业应用。该文档适用于在VMware workstation平台安装SUSE Linux Enterprise Server虚拟机。 1.安装准备 1.1安装平台 Windows…

设计一个网页爬虫

定义 User Case 和 约束 注意&#xff1a;没有一个面试官会阐述清楚问题&#xff0c;我们需要定义Use case和约束 Use cases 我们的作用域只是处理以下Use Case&#xff1a; Service 爬取一批 url 生成包含搜索词的单词到页面的反向索引给页面生成标题和片段– 标题和片段是…

【机器学习】机器学习变量分析第02课

当我们谈论用机器学习来预测咖啡店的销售额时&#xff0c;我们实际上是在处理一系列与咖啡销售相关的变量。这些变量就像是我们用来理解销售情况的“线索”或“指标”。那么&#xff0c;让我们用通俗易懂的方式来聊聊这些变量是怎么工作的。 特征变量&#xff1a;咖啡店的“档…

Spring MVC学习之——自定义日期转化器

日期转换器 在数据库中的日期数据是date类型&#xff0c;而如何我们想在页面自己添加数据&#xff0c;一般是使用年-月-日的形式&#xff0c;这种形式不仅date类型接收不到&#xff0c;而且传来的是String类型&#xff0c;此时&#xff0c;我们就可以自定义日期转换器来接收数…

JVM知识总结

1.概述 JVM指的是Java虚拟机&#xff0c;本质上是一个运行在计算机上的程序&#xff0c;他的职责是运行Java字节码文件&#xff0c;作用是为了支持跨平台特性。 功能&#xff1a; 装载字节码&#xff0c;解释/编译为机器码 管理数据存储和垃圾回收 优化热点代码提升效率 …

【数学建模】2024年华数杯国际赛B题-光伏发电Photovoltaic Power 思路、代码、参考论文

1 问题背景 中国电力构成包括传统能源(如煤炭、石油、天然气)、可再生能源(如水电、风能、太阳能、核能)和其他形式的电力。这些发电模式在满足中国巨大的电力需求方面发挥着至关重要的作用。据最新数据显示&#xff0c;中国总发电量超过20万亿千瓦时&#xff0c;居世界第一。…

【征服redis8】Redis的AOF持久化

Redis 支持多种持久化方式来保证数据的可靠性和持久性。前面我们介绍了RDB方式。我们我们介绍第二种方式——AOF&#xff08;Append Only File&#xff09;机制是一种常用的持久化方式&#xff0c;它记录了所有对 Redis 数据库进行修改的命令&#xff0c;在 Redis 重启时可以使…

echarts X轴数据过多导致重叠展示不全问题(已解决)

问题 x轴数据过多导致坐标轴数据重叠 修改后 List item interval为0代表每个标签都显示&#xff0c;即间隔为0&#xff01; 将其设置为我们想要的数值即可。 xAxis: {type: "time",splitLine: {show: false,},axisLine: {show: false,lineStyle: {color: &qu…

Spring MVC学习——解决请求参数中文乱码

解决请求参数中文乱码问题 1.POST请求方式解决乱码问题 在web.xml里面设置编码过滤器 <filter><filter-name>CharacterEncodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><…

lua使用resty.http做nginx反向代理(https请求,docker容器化部署集群),一个域名多项目转发

下载使用 链接&#xff1a;https://pan.baidu.com/s/1uQ7yCzQsPWsF6xavFTpbZg 提取码&#xff1a;htay –来自百度网盘超级会员V5的分享 在根目录下执行: # 从 github 上下载文件 git clone https://github.com/ledgetech/lua-resty-http.git # 将 lua-resty-http/lib/ 下的 r…