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. 第六种方法...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...