当前位置: 首页 > news >正文

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';  //输出:falsestd::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'; //输出:truestd::cout << std::is_destructible<int>::value << '\n';     //输出: truestd::cout << std::is_destructible<int[]>::value << '\n';   //输出:falsestd::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'; //输出:falsestd::cout << std::is_default_constructible<Y>::value << '\n'; //输出:truestd::cout << std::is_default_constructible<int>::value << '\n'; //输出:truestd::cout << std::is_default_constructible<int[]>::value << '\n'; //输出:falsereturn 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 placeexplicit 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 placeexplicit 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 placeexplicit 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 storagereturn *this;}_NODISCARD constexpr const _Storage_t& _Storage() const& noexcept { // access this variant's storagereturn *this;}_NODISCARD constexpr _Storage_t&& _Storage() && noexcept { // access this variant's storagereturn _STD move(*this);}_NODISCARD constexpr const _Storage_t&& _Storage() const&& noexcept { // access this variant's storagereturn _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_storageif 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 >= 64return _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_npostypename _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_nposreturn _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_2return _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 activestatic_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 activeconstexpr 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 _Idxstatic_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 _Tyconstexpr 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.总结

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

相关文章:

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.…...

实验一 安装和使用Oracle数据库

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…...

软件工程研究生后期总结

写这篇随笔的时候&#xff0c;我已经处于研究生阶段的后期&#xff0c;只剩下一个硕论答辩即可结束研究生生涯。趁有闲暇时间&#xff0c;我希望可以从实习、兼职、论文和求职等几个角度重新整理一下研究生后期的工作和收获&#xff0c;以及对未来工作和生活做出展望。 首先简…...

Java爬虫爬取图片壁纸

Java爬虫 以sougou图片为例&#xff1a;https://pic.sogou.com/ JDK17、SpringBoot3.2.X、hutool5.8.24实现Java爬虫&#xff0c;爬取页面图片 项目介绍 开发工具&#xff1a;IDEA2023.2.5 JDK&#xff1a;Java17 SpringBoot&#xff1a;3.2.x 通过 SpringBoot 快速构建开发环境…...

红队打靶练习:HOLYNIX: V1

目录 信息收集 1、arp 2、netdiscover 3、nmap 4、nikto whatweb 目录探测 1、gobuster 2、dirsearch 3、dirb 4、feroxbuster WEB sqlmap 1、爆库 2、爆表 3、爆列 4、爆字段 后台登录 1、文件上传 2、文件包含 3、越权漏洞 反弹shell 提权 总结 信息…...

elasticsearch[二]-DSL查询语法:全文检索、精准查询(term/range)、地理坐标查询(矩阵、范围)、复合查询(相关性算法)、布尔查询

ES-DSL查询语法&#xff08;全文检索、精准查询、地理坐标查询&#xff09; 1.DSL查询文档 elasticsearch 的查询依然是基于 JSON 风格的 DSL 来实现的。 1.1.DSL 查询分类 Elasticsearch 提供了基于 JSON 的 DSL&#xff08;Domain Specific Language&#xff09;来定义查…...

Microsoft Word 设置底纹

Microsoft Word 设置底纹 References 打开文档页面&#xff0c;选中特定段落或全部文档 在“段落”中单击“边框”下三角按钮 在列表中选择“边框和底纹”选项 在“边框和底纹”对话框中单击“底纹”选项卡 在图案样式和图案颜色列表中设置合适颜色的底纹&#xff0c;单击“确…...

【大数据】Flink 详解(九):SQL 篇 Ⅱ

《Flink 详解》系列&#xff08;已完结&#xff09;&#xff0c;共包含以下 10 10 10 篇文章&#xff1a; 【大数据】Flink 详解&#xff08;一&#xff09;&#xff1a;基础篇【大数据】Flink 详解&#xff08;二&#xff09;&#xff1a;核心篇 Ⅰ【大数据】Flink 详解&…...

workflow源码解析:GoTask

关于go task 提供了另一种更简单的使用计算任务的方法&#xff0c;模仿go语言实现的go task。 使用go task来实计算任务无需定义输入与输出&#xff0c;所有数据通过函数参数传递。 与ThreadTask 区别 ThreadTask 是有模板&#xff0c;IN 和 OUT&#xff0c; ThreadTask 依赖…...

SpringMVC入门案例

引言 Spring MVC是一个基于MVC架构的Web框架&#xff0c;它的主要作用是帮助开发者构建Web应用程序。它提供了一个强大的模型驱动的开发方式&#xff0c;可以帮助开发者实现Web应用程序的各种功能&#xff0c;如请求处理、数据绑定、视图渲染、异常处理等。 开发步骤 1.创建we…...

Docker本地私有仓库搭建配置指导

一、说明 因内网主机需要拉取镜像进行Docker应用&#xff0c;因此需要一台带外主机作为内网私有仓库来提供内外其他docker业务主机使用。参考架构如下&#xff1a; 相关资源&#xff1a;加密、Distribution registry、Create and Configure Docker Registry、Registry部署、D…...

python 通过定时任务执行pytest case

这段Python代码使用了schedule库来安排一个任务&#xff0c;在每天的22:50时运行。这个任务执行一个命令来运行pytest&#xff0c;并生成一个报告。 代码开始时将job_done变量设为False&#xff0c;然后运行预定的任务。一旦任务完成&#xff0c;将job_done设置为True并跳出循…...

算法面试题:合并两个有序链表

描述&#xff1a;给定两个按非递减顺序排列的链表&#xff0c;合并两个链表&#xff0c;并将结果按非递减顺序排列。 例如&#xff1a; # 链表 1: 1 -> 2 -> 4 # 链表 2: 1 -> 3 -> 4合并后的链表应该是&#xff1a;1 -> 1 -> 2 -> 3 -> 4 -> 4 …...

LaWGPT安装和使用教程的复现版本【细节满满】

文章目录 前言一、下载和部署1.1 下载1.2 环境安装1.3 模型推理 总结 前言 LaWGPT 是一系列基于中文法律知识的开源大语言模型。该系列模型在通用中文基座模型&#xff08;如 Chinese-LLaMA、ChatGLM等&#xff09;的基础上扩充法律领域专有词表、大规模中文法律语料预训练&am…...

西门子博途用SCL语言写的入栈出栈

1.用户登录 #pragma code ("useadmin.dll") #include "PWRT_api.h" #pragma code() PWRTLogin(1) 2.用户退出 #pragma code ("useadmin.dll") #include "PWRT_api.h" #pragma code() PWRTLogout(); 3.画面跳转 SetPictureName("P…...

密码产品推介 | 沃通安全电子签章系统(ES-1)

产品介绍 沃通安全电子签章系统&#xff08;ES-1&#xff09;是一款基于密码技术、完全自主研发的商用密码产品&#xff0c;严格遵循国家密码管理局制定的相关标准&#xff0c;可为企业和个人提供安全、合规的电子签章功能服务。产品的主要用途是为各类文书、合同、表单等电子…...

蓝桥杯真题(Python)每日练Day1

说明&#xff1a;在CSP认证的基础上&#xff08;可以看看本人CSP打卡系列的博客&#xff09;备赛2024蓝桥杯&#xff08;Python&#xff09;&#xff0c;本人专业&#xff1a;大数据与数据科学 因此对python要求熟练掌握&#xff0c;通过练习蓝桥杯既能熟悉语法又能锻炼算法和思…...

IDEA怎么用Devtools热部署

IDEA怎么用Devtools热部署 大家知道在项目开发过程中&#xff0c;有时候会改动代码逻辑或者修改数据结构&#xff0c;为了能使改动的代码生效&#xff0c;往往需要重启应用查看改变效果&#xff0c;这样会相当耗费时间。 重启应用其实就是重新编译生成新的Class文件&#xff0…...

boost.circular_buffer的使用和介绍

C 文章目录 C 很多时候&#xff0c;我们需要在内存中记录最近一段时间的数据&#xff0c;如操作记录等。由于这部分数据记录在内存中&#xff0c;因此并不能无限递增&#xff0c;一般有容量限制&#xff0c;超过后就将最开始的数据移除掉。在stl中并没有这样的数据结构&#xf…...

深入理解Java中的ThreadLocal

第1章&#xff1a;引言 大家好&#xff0c;我是小黑。今天咱们来聊聊ThreadLocal。首先&#xff0c;让咱们先搞清楚&#xff0c;ThreadLocal是个什么玩意儿。简单说&#xff0c;ThreadLocal可以让咱们在每个线程中创建一个变量的“私有副本”。这就意味着&#xff0c;每个线程…...

python打卡day49

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

JVM垃圾回收机制全解析

Java虚拟机&#xff08;JVM&#xff09;中的垃圾收集器&#xff08;Garbage Collector&#xff0c;简称GC&#xff09;是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象&#xff0c;从而释放内存空间&#xff0c;避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

第25节 Node.js 断言测试

Node.js的assert模块主要用于编写程序的单元测试时使用&#xff0c;通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试&#xff0c;通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

Web 架构之 CDN 加速原理与落地实践

文章目录 一、思维导图二、正文内容&#xff08;一&#xff09;CDN 基础概念1. 定义2. 组成部分 &#xff08;二&#xff09;CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 &#xff08;三&#xff09;CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 &#xf…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

LLMs 系列实操科普(1)

写在前面&#xff1a; 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容&#xff0c;原视频时长 ~130 分钟&#xff0c;以实操演示主流的一些 LLMs 的使用&#xff0c;由于涉及到实操&#xff0c;实际上并不适合以文字整理&#xff0c;但还是决定尽量整理一份笔…...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...