C++三剑客之std::variant(一)
1简介
C++17的三剑客分别是std::optional, std::any, std::vairant。今天主要讲std::variant。std::variant的定义如下:
template< class... Types >
class variant;
类模板 std::variant
表示一个类型安全的联合体(以下称“变化体”)。std::variant
的一个实例在任意时刻要么保有它的一个可选类型之一的值,要么在错误情况下无值(此状态难以达成,见 valueless_by_exception);从功能上讲,它就跟union的功能差不多,但却比union更高级;variant主要是为了提供更安全的union。举个例子union里面不能有string这种类型,但std::variant却可以,还可以支持更多复杂类型,如map等
2辅助类
2.1 std::monostate
为了支持第一个类型没有默认构造函数的variant对象,提供了一个特殊的helper类型:std::monostate。类型std::monostate的对象总是具有相同的状态,因此,它们总是相等的。它自己的目的是表示另一种类型,这样variant就没有任何其他类型的值。也就是说,std::monostate可以作为第一种替代类型,使变体类型默认为可构造。
std::variant<std::monostate, int> test; // OK
std::cout << "index: " << test.index(); //输出: 0
我们可以简单理解std::monostate是个占位符。
2.2 std::bad_variant_access
std::bad_variant_access
是下列情形中抛出的异常类型:
- 以不匹配当前活跃可选项的下标或类型调用 std::get(std::variant)
- 调用 std::visit 观览因异常无值 (valueless_by_exception) 的
variant
示例如下:
#include <variant>
#include <iostream>int main()
{std::variant<int, float> v;v = 12;try {std::get<float>(v);}catch(const std::bad_variant_access& e) {std::cout << e.what() << '\n';}
}
可能的输出:
bad_variant_access
2.3 std::variant_size和std::variant_size_v
提供作为编译时常量表达式,对可有 cv 限定的 variant
中可选项数量的访问,示例如下:
#include <any>
#include <cstdio>
#include <variant>static_assert(std::variant_size_v<std::variant<>> == 0);
static_assert(std::variant_size_v<std::variant<int>> == 1);
static_assert(std::variant_size_v<std::variant<int, int>> == 2);
static_assert(std::variant_size_v<std::variant<int, int, int>> == 3);
static_assert(std::variant_size_v<std::variant<int, float, double>> == 3);
static_assert(std::variant_size_v<std::variant<std::monostate, void>> == 2);
static_assert(std::variant_size_v<std::variant<const int, const float>> == 2);
static_assert(std::variant_size_v<std::variant<std::variant<std::any>>> == 1);int main() { std::puts("All static assertions passed."); }
输出:
All static assertions passed.
2.4 std::variant_alternative和std::variant_alternative_t
提供对 variant
的类型编译时下标访问,示例如下:
#include <variant>
#include <iostream>using my_variant = std::variant<int, float>;
static_assert(std::is_same_v<int, std::variant_alternative_t<0, my_variant>>);
static_assert(std::is_same_v<float, std::variant_alternative_t<1, my_variant>>);
// variant 类型上的 cv 限定传播给提取出的可选项类型。
static_assert(std::is_same_v<const int, std::variant_alternative_t<0, const my_variant>>);int main()
{std::cout << "All static assertions passed\n";
}
输出:
All static assertions passed
3 std::variant的操作
std::variant提供的主要操作有:
操作 | 说明 |
---|---|
constructors | 创建一个variant对象(可能调用底层类型的构造函数) |
destructor | 销毁一个variant对象 |
emplace<T>() | 为具有类型T的备选项分配一个新值 |
emplace<Idx>() | 为索引Idx的备选项分配一个新值 |
= | 分配一个新值 |
index() | 返回当前备选项的索引 |
holds_alternative<T>() | 返回类型T是否有值 |
==, !=, <, <=, >, >= | 比较variant对象 |
swap() | 交换两个对象的值 |
hash<> | 函数对象类型来计算哈希值 |
valueless_by_exception() | 返回该变量是否由于异常而没有值 |
get<T>() | 返回备选项类型为T的值或抛出异常(如果没有类型为T的值) |
get<Idx>() | 返回备选项索引为idx的值或抛出异常(如果没有索引为idx的值) |
get_if<T>() | 返回指向类型为T指针或返回nullptr(如果没有类型为T的值) |
get_if<Idx>() | 返回指向索引Idx的指针或nullpt(如果没有索引为idx的值) |
visit() | 为当前备选项执行操作 |
3.1 定义
直接定义std::variant,如:
std::variant<std::uint32_t, double, std::string, std::int32_t> t;
可以对 t 赋初值,对于基本类型,它是0、false还是nullptr。如果传递一个值进行初始化,则使用最佳匹配类型,如:
std::variant<std::uint32_t, double, std::string, std::int32_t> t{ 25 };
cout << t.index(); //输出: 3
要传递多个值进行初始化,必须使用in_place_type或in_place_index标记:
std::variant<std::complex<double>> t1{1.0,564.0}; // ERROR
std::variant<std::complex<double>> t2{std::in_place_type<std::complex<double>>,
322.0, 2323.0};
std::variant<std::complex<double>> t3{std::in_place_index<0>, 35.0, 8.0};
如果初始化过程中出现歧义或匹配问题,可以用in_place_index标签来解决,如:
std::variant<int, int> t1{std::in_place_index<1>, 77}; // init 2nd int
std::variant<int, long> t2{std::in_place_index<1>, 77}; // init long, not int
std::cout << t2.index(); // prints 1
3.2 访问值
std::variant可以通std::get来获取或修改值。
std::variant<std::uint32_t, double, std::string, std::int32_t> var;
auto x = std::get<double>(var);
auto y = std::get<4>(var); // compile-time ERROR: no 4th alternative
auto c = std::get<int>(var); try{auto m = std::get<std::string>(var); // throws exception (first int currently set)auto n = std::get<0>(var); // OK, i==0auto o = std::get<1>(var); // throws exception (other int currently set)
}
catch (const std::bad_variant_access& e) { // in case of an invalid accessstd::cout << "Exception: " << e.what() << '\n';
}
std::get_if用来判断某项是否存在
#include <variant>
#include <iostream>int main()
{std::variant<int, float> v{12};if(auto pval = std::get_if<int>(&v))std::cout << "variant value: " << *pval << '\n'; //输出: 12else std::cout << "failed to get value!" << '\n';
}
还可以通过std::visit来访问值:
struct stValueVisitor {void operator()(int i) { cout << "int: " << i << '\n'; }void operator()(float f) { cout << "float: " << f << '\n'; }void operator()(const std::string& s) { cout << "str: " << s << '\n'; }
};int main() {std::variant<int, float, string> value = 65.2;std::visit(stValueVisitor{}, value); // 输出 float: 65.2return 0;}
也可以利用C++17 新增的 overloaded 模板,可以直接生成匿名访问器,简化代码, 下面的代码是等价的:
int main() {std::variant<int, float, string> t= 56.4;std::visit(overloaded{void operator()(int i) { cout << "int: " << i << '\n'; }void operator()(float f) { cout << "float: " << f << '\n'; }void operator()(const std::string& s) { cout << "str: " << s << '\n'; }},t);}
接下来还有第三种方法来访问std::variant :
int main()
{std::variant<int, float, string> t = 16.4;std::visit([&](auto &&arg) {using T = std::decay_t<decltype(arg)>; // 类型退化,去掉类型中的const 以及 &if constexpr(std::is_same_v<T,int>) {cout << "int: " << arg << '\n';} else if constexpr(std::is_same_v<T,float>){cout<< "float: "<< arg <<'\n';} else if constexpr(std::is_same_v<T,std::string>){cout<< "str: "<< arg <<'\n';}}, t);
}
这里我们可以看出来第三种写法比第一种的优势在哪里了:编译期推断。
第三种方法由于使用了constexpr 进行 if 分支的判断,因此是在编译期运行,而第一种方法是运行期进行类型判断,效率是不同的。
第二种方法和模板一样,也是编译期推断的,因此效率也是很高的,所以我们应当尽量使用 std::visit 方法来访问variant 变量
另外std::visit 还有一个好处是,它的参数列表是不定长的,我们可以传入多个variant 变量
template <class Visitor, class... Variants>
constexpr visit(Visitor&& vis, Variant&&... vars);
3.3 修改值
用std::get来修改值,如:
std::variant<std::uint32_t, double, std::string, std::int32_t> t;
std::get<double>(t) = 1.0;
cout << t.index(); //输出: 1
std::get<std::uint32_t>(t) = 34;
cout << t.index(); //输出: 0
std::get<2>(t) = "4343636";
cout << t.index(); //输出:2
也可以用std::get_if来修改值,如:
std::variant<std::uint32_t, double, std::string, std::int32_t> t;
if (auto p = std::get_if<1>(&t)) { // if second int set*p = 42.2; // modify it
}
4 总结
以上都是我日常工作中对std::variant的用法的总结;做技术,要知其然,更要知其所以然,后面我将从std::varant的源码实现上继续分析它的原理,敬请期待。。。
参考
std::visit
std::variant
相关文章:
C++三剑客之std::variant(一)
1简介 C17的三剑客分别是std::optional, std::any, std::vairant。今天主要讲std::variant。std::variant的定义如下: template< class... Types > class variant; 类模板 std::variant 表示一个类型安全的联合体(以下称“变化体”)…...

新火种AI|AI正在让汽车成为“消费电子产品”
作者:一号 编辑:小迪 AI正在让汽车产品消费电子化 12月28日,铺垫许久的小米汽车首款产品——小米SU7正式在北京亮相。命里注定要造“电车”的雷军,在台上重磅发布了小米的五大自研核心技术。在车型设计、新能源技术以及智能科技…...
Docker六 | Docker Compose容器编排
目录 Docker Compose 基本概念 使用步骤 常用命令 Docker Compose Docker-Compose是Docker官方的开源项目,负责实现对Docker容器集群的快速编排。Compose可以管理多个Docker容器组成一个应用。 需要定义一个YAML格式的配置文件docker-compose.yml,…...

Oraclelinux部署Oracle服务
采用图形化界面 user用户 oracle用户 #清屏 clear #设置主机名 hostnamectl set-hostname ceshidb sed -i 1,2 s/^/#/ /etc/hosts echo "127.0.0.1 ceshidb" >> /etc/hosts echo "::1 ceshidb" >> /etc/hosts ping -c 5…...

Ubuntu安装K8S(1.28版本,基于containrd)
原文网址:Ubuntu安装K8S(1.28版本,基于containrd)-CSDN博客 简介 本文介绍Ubuntu安装K8S的方法。 官网文档:这里 1.安装K8S 1.让apt支持SSL传输 sudo apt-get update sudo apt-get -y install apt-transport-https ca-certi…...

Linux 线程安全 (2)
文章目录 线程同步概念条件变量使用生产消费模型信号量的使用读写锁的使用 Linux 线程安全 (1) 线程同步概念 竞态条件:因为时序问题,而导致程序异常. 饥饿问题:只使用互相锁保证线程安全时,锁资源总被某…...
异或运算^简述
异或运算:^ 两个变量之间异或运算时,其二进制位相同取0,不同取1. 示例:a10 (0b 0000 1010) b3 (0b 0000 0011) a^b9(0b 0000 1001) 据此可以推算异或运算"^"有以下特性: a^a0 (0b 0000 0000)…...

Google Play上架:2023年度总结报告
今天是2023年的最后一个工作日,今天用来总结一下2023年关于谷歌商店上架的相关政策改动和对应的拒审解决方法。 目录 政策更新与改动2023 年 2 月 22 日2023 年 4 月5 日2023 年 7 月 12 日2023 年 10 月 25 日 开发者计划政策拒审邮件内容和解决办法 政策更新与改…...
JAVA进化史: JDK10特性及说明
DK 10(Java Development Kit 10)是Java平台的一个版本,于2018年3月发布。尽管相对于之前的版本,JDK 10的变化较为温和,但仍然引入了一些新特性和改进,以下是其中一些主要特性,并带有相应的示例说…...
第二百三十四回
文章目录 1.概念介绍2.使用方法2.1 NumberPicker2.2 CupertinoPicker 3.示例代码4.内容总结 我们在上一章回中介绍了"如何在任意位置显示PopupMenu"相关的内容,本章回中将介绍如何实现NumberPicker.闲话休提,让我们一起Talk Flutter吧。 1.概念…...

{MySQL} 数据库约束 表的关系 新增删除 修改 查询
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、数据库约束1.1约束类型:1.2 NULL约束1.3unique 唯一约束1.4 DEFAULT:默认值约束1.5 PRIMARY KEY:主键约束1.6 FOREIGN K…...

【JVM】虚拟机的组成+字节码文件组成+类的生命周期
什么是JVM? JVM 本质上是一个运行在计算机上的程序,他的职责是运行Java字节码文件。 JVM的功能 1.解释和运行:对字节码文件中的指令实时的解释成机器码让计算机执行。 2.内存管理:自动为对象、方法等分配内存,自动…...
pip 下载太慢的解决办法,pip换国内源,pip换源
用pip安装python包的时候,如果系统没有进行相关设置,则用的源服务器是国外的,在国内访问非常慢,我们需要换成国内的源服务器,pip换源通过如下命令: pip config set global.index-url <源地址> 一、…...

OKCC语音机器人的人机耦合来啦
目前市场上语音机器人的外呼形式基本就分为三种,一种纯AI外呼,第二种也是目前主流的AI外呼转人工。那么第三种也可能是未来的一种趋势,人机耦合,或者也叫人机协同。 那么什么是人机耦合呢? 人机耦合是为真人坐席创造相…...
有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。 示例 1: 输入:nums [-4,-1,0,3,10] 输出:[0,1,9,16,100] 解释:平方后,数组变为 …...

Java虚拟机中的垃圾回收
2 垃圾回收 2.1 判断一个对象是否可回收 2.1.1 引用计数法 如果一个对象被另一个对象引用,那么它的引用计数加一,如果那个对象不再引用它了,那么引用计数减一。当引用计数为 0 时,该对象就应该被垃圾回收了。 但是下面这种互相…...

Vscode新手安装与使用
安装与版本选择 VS Code 有两个不同的发布渠道:一个是我们经常使用的稳定版(Stable),每个月发布一个主版本;另外一个发布渠道叫做 Insiders,每周一到周五 UTC 时间早上6点从最新的代码发布一个版本&#x…...

以元旦为题的诗词(二)
都放假了吧,都有空了吧,可坐下来好好学学诗词,好好写些诗词了吧,我先来几首,你实在不行,去百度或者小程序搜索《美诗计》写一写 元旦 去年元日落寒灰,今岁清明在此杯 老眼看书如梦寐ÿ…...

饥荒Mod 开发(二一):超大便携背包,超大物品栏,永久保鲜
饥荒Mod 开发(二十):显示打怪伤害值 饥荒Mod 开发(二二):显示物品信息 源码 游戏中的物品栏容量实在太小了,虽然可以放在箱子里面但是真的很不方便,外出一趟不容易看到东西都不能捡。实在是虐心。 游戏中的食物还有变质机制,时间长了就不能吃了,玩这个游戏心里压力真是太…...
js 七种继承方法
目录 1. 第一种方法:原型链继承 2. 第二种方法:构造函数继承(call继承) 3. 第三种方法:组合式继承 4. 第四种方法:拷贝继承 5. 第五种方法:原型式继承 6. 第六种方法...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving
地址:LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂,正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...

基于开源AI智能名片链动2 + 1模式S2B2C商城小程序的沉浸式体验营销研究
摘要:在消费市场竞争日益激烈的当下,传统体验营销方式存在诸多局限。本文聚焦开源AI智能名片链动2 1模式S2B2C商城小程序,探讨其在沉浸式体验营销中的应用。通过对比传统品鉴、工厂参观等初级体验方式,分析沉浸式体验的优势与价值…...

对象回调初步研究
_OBJECT_TYPE结构分析 在介绍什么是对象回调前,首先要熟悉下结构 以我们上篇线程回调介绍过的导出的PsProcessType 结构为例,用_OBJECT_TYPE这个结构来解析它,0x80处就是今天要介绍的回调链表,但是先不着急,先把目光…...
智能体革命:企业如何构建自主决策的AI代理?
OpenAI智能代理构建实用指南详解 随着大型语言模型(LLM)在推理、多模态理解和工具调用能力上的进步,智能代理(Agents)成为自动化领域的新突破。与传统软件仅帮助用户自动化流程不同,智能代理能够自主执行工…...

Qt/C++学习系列之列表使用记录
Qt/C学习系列之列表使用记录 前言列表的初始化界面初始化设置名称获取简单设置 单元格存储总结 前言 列表的使用主要基于QTableWidget控件,同步使用QTableWidgetItem进行单元格的设置,最后可以使用QAxObject进行单元格的数据读出将数据进行存储。接下来…...

机器学习复习3--模型评估
误差与过拟合 我们将学习器对样本的实际预测结果与样本的真实值之间的差异称为:误差(error)。 误差定义: ①在训练集上的误差称为训练误差(training error)或经验误差(empirical error&#x…...
后端解决跨域问题的三种方案:注解配置 vs 全局配置 vs 过滤器配置(附完整代码详解)
文章目录 一、引言:跨域问题的本质与解决方案分类解决方案分类二、方案一:`WebMvcConfigurer` 全局配置(推荐)1. 核心代码(你提供的 `CorsConfig` 示例)2. 代码详解3. 优点4. 注意事项三、方案二:`CorsFilter` 过滤器配置(传统方式)1. 核心代码(你提供的 `ResourcesC…...