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数据库
🕺作者: 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 😘欢迎关注:👍点赞🙌收藏✍️留言 🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的…...

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

Java爬虫爬取图片壁纸
Java爬虫 以sougou图片为例:https://pic.sogou.com/ JDK17、SpringBoot3.2.X、hutool5.8.24实现Java爬虫,爬取页面图片 项目介绍 开发工具:IDEA2023.2.5 JDK:Java17 SpringBoot: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查询语法(全文检索、精准查询、地理坐标查询) 1.DSL查询文档 elasticsearch 的查询依然是基于 JSON 风格的 DSL 来实现的。 1.1.DSL 查询分类 Elasticsearch 提供了基于 JSON 的 DSL(Domain Specific Language)来定义查…...

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

【大数据】Flink 详解(九):SQL 篇 Ⅱ
《Flink 详解》系列(已完结),共包含以下 10 10 10 篇文章: 【大数据】Flink 详解(一):基础篇【大数据】Flink 详解(二):核心篇 Ⅰ【大数据】Flink 详解&…...

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

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

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

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

算法面试题:合并两个有序链表
描述:给定两个按非递减顺序排列的链表,合并两个链表,并将结果按非递减顺序排列。 例如: # 链表 1: 1 -> 2 -> 4 # 链表 2: 1 -> 3 -> 4合并后的链表应该是:1 -> 1 -> 2 -> 3 -> 4 -> 4 …...

LaWGPT安装和使用教程的复现版本【细节满满】
文章目录 前言一、下载和部署1.1 下载1.2 环境安装1.3 模型推理 总结 前言 LaWGPT 是一系列基于中文法律知识的开源大语言模型。该系列模型在通用中文基座模型(如 Chinese-LLaMA、ChatGLM等)的基础上扩充法律领域专有词表、大规模中文法律语料预训练&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)
产品介绍 沃通安全电子签章系统(ES-1)是一款基于密码技术、完全自主研发的商用密码产品,严格遵循国家密码管理局制定的相关标准,可为企业和个人提供安全、合规的电子签章功能服务。产品的主要用途是为各类文书、合同、表单等电子…...

蓝桥杯真题(Python)每日练Day1
说明:在CSP认证的基础上(可以看看本人CSP打卡系列的博客)备赛2024蓝桥杯(Python),本人专业:大数据与数据科学 因此对python要求熟练掌握,通过练习蓝桥杯既能熟悉语法又能锻炼算法和思…...

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

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

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

【重点】【DP】300. 最长递增子序列
题目 更好的方法是耐心排序,参见《算法小抄》的内容!!! 法1:DP 基础解法必须掌握!!! class Solution {public int lengthOfLIS(int[] nums) {if (nums null || nums.length 0) …...

使用freessl为网站获取https证书及配置详细步骤
文章目录 一、进入freessl网站二、修改域名解析记录三、创建证书四、配置证书五、服务启动 一、进入freessl网站 首先进入freessl网站,需要注册一个账号 freessl网站 进入网站后填写自己的域名 接下来要求进行DCV配置 二、修改域名解析记录 到域名管理处编辑域名…...

Java-初识正则表达式 以及 练习
目录 什么是正则表达式? 1. 正则表达式---字符类(一个大括号匹配一个字符): 2. 正则表达式---预字符类(也是匹配一个字符): 正则表达式---数量词 (可以匹配多个字符)…...

【Flutter 问题系列第 80 篇】TextField 输入框组件限制可输入的最大长度后,输入的内容中包含表情符号时,获取输入的内容数还是会超出限制的问题
这是【Flutter 问题系列第 80 篇】,如果觉得有用的话,欢迎关注专栏。 博文当前所用 Flutter SDK:3.10.5、Dart SDK:3.0.5 一:问题描述 在输入用户名称、简介等内容时,一般我们都会限制输入框内最大可输入…...

漏洞检测和评估【网站子域扫描工具02】
上一篇:爬取目标网站的域名和子域名【网站子域扫描工具01】 在Python中,有一些流行的漏洞扫描库可以对子域进行漏洞扫描和评估,比如Nmap、Sublist3r等。 1.端口扫描 以下是一个简单的示例代码,展示了如何使用Nmap进行基本的端口扫…...
压力测试+接口测试(工具jmeter)
jmeter是apache公司基于java开发的一款开源压力测试工具,体积小,功能全,使用方便,是一个比较轻量级的测试工具,使用起来非常简单。因 为jmeter是java开发的,所以运行的时候必须先要安装jdk才可以。jmeter是…...

LeetCode 46 全排列
题目描述 全排列 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1: 输入:nums [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2: 输入…...

npm install 无反应 npm run serve 无反应
说明情况:其实最开始我就是发现我跟着黑马的苍穹外卖的前端day2的环境搭建做的时候,到这一步出现了问题,无论我怎么 npm install 和 npm run serve 都没有像黑马一样有很多东西进行加载,因此我换了一种方法 1.在这个文件夹下cmd …...

JAVAEE初阶 文件IO(二)
文件IO 一. 文件流1.1 字节流 inputStream(1) try with resources方法 1.2 read方法(1) 第一个read方法(2) 第二个read方法(3) read的第三个方法 1.3 字节流 OutoutStream1.4 字符流(1) reader(2) writer 一. 文件流 1.1 字节流 inputStream 在字节流中,我们使用inputStream和…...

Golang 三数之和+ 四数之和 leetcode15、18 双指针法
文章目录 三数之和 leetcode15map记录 失败!超出限制双指针法 四数之和 leetcode18 三数之和 leetcode15 知识补充: map的key值必须是可以比较运算的类型,不可以是函数、map、slice map记录 失败!超出限制 //得到结果后再去重 失…...