泛形variant+visit

泛形variant+visit

1.引言

python里可以让一个变量变成不同的类型,拥有不同的值,且根据不同的类型执行不同的操作,当不同的类型拥有同样的函数时,这样我们就不用再重复写一堆代码了

但如果在c++中实现类似的功能,比较经典的处理方式是用虚函数 + 子类重写的方式,

c++ 复制代码
class Base{
    virtual void accept(visitor) = 0;
}
class sub1:Base{
    void accept(visitor){
    visitor->visit(this)
}
}
class sub2:Base{
void accept(visitor){
    visitor->visit(this)
}

这样的话代码的冗余度就高了,且每次添加新都需要新建一个类

有没有更简单一些的方式呢,接下来的variant+visit能够很好的解决该问题

2. variant

std::variant 是 C++17 标准中引入的一种数据类型,它允许在一个变量中存储多种不同类型的值,这些值被称为"备选项"或"可替代项"。std::variant 本质上是一种类型安全的联合(Union)类型

2.1 特点

  1. 类型安全std::variant 确保在编译时检查类型,因此可以避免运行时的类型错误。
  2. 多态值std::variant 可以存储不同的数据类型,这使得它非常灵活,可以在一种类型安全的情况下处理多态值。
  3. 访问备选项 :使用 std::get<>()std::get_if<>() 可以访问 std::variant 中存储的备选项。此外,std::visit() 函数提供了一种通用的方式来访问 std::variant 中的值,类似于多态行为。
  4. 异常安全 :与使用裸指针和类型转换相比,std::variant 提供了更好的异常安全性,因为它保证只能存储其所允许的类型。
  5. 替代方案 :在以前的 C++ 版本中,通常会使用联合(Union)类型来实现多态值的存储,但这种方法没有提供类型安全性,并且通常需要显式的类型检查和转换。std::variant 提供了一个更安全、更方便的替代方案。

2.2 简单示例

c++ 复制代码
#include <iostream>
#include <variant>
#include <string>

int main() {
    std::variant<int, double, std::string> v;
    v = 10;
    std::cout << "Value: " << std::get<int>(v) << std::endl;

    v = 3.14;
    std::cout << "Value: " << std::get<double>(v) << std::endl;

    v = "Hello, variant!";
    std::cout << "Value: " << std::get<std::string>(v) << std::endl;

    return 0;
}

如果只是简单的使用variant, 从上例可以看出,每次都要往std::get<>里传入确定的类型,这样的话,只是实现的一个带有类型擦除的不同类型的存储结构,无法根据类型做不同的执行

3. variant+visit

std::visit 用于访问 std::variant 中存储的值。使用方式如下

3.1 示例1------多类型同名函数调用

c++ 复制代码
#include <iostream>
#include <string>
#include <variant>
#include <vector>
 
// variant
using var_t = std::variant<int, long, double, std::string>;

int main() {
    std::string s = "\n";
    std::vector<var_t> vec = {10, 15l, 1.5, "hello"};
    for(auto&& v: vec) {
        std::visit([&](auto&& arg){
            std::cout << arg; 
            std::cout << s;
        }, v);
    }
}
  • output
sh 复制代码
10
15
1.5
hello

3.2 示例2------返回值

c++ 复制代码
#include <iostream>
#include <string>
#include <variant>
#include <vector>
 
// variant
using var_t = std::variant<int, long, double, std::string>;

int main() {
    std::string s = "\n";
    std::vector<var_t> vec = {10, 15l, 1.5, "hello"};
    for(auto&& v: vec) {
        //返回w的值为不同的值相加
     var_t w = std::visit([](auto&& arg) -> var_t {return arg + arg;}, v);
    }
}

3.3 示例3------类型匹配1

  • 类型匹配 即可根据不同的类型做不同的执行
c++ 复制代码
#include <iostream>
#include <variant>
#include <string>
struct PrintVisitor {
    void operator()(int value) const {
        std::cout << "Integer value: " << value << std::endl;
    }
    void operator()(double value) const {
        std::cout << "Double value: " << value << std::endl;
    }
    void operator()(const std::string& value) const {
        std::cout << "String value: " << value << std::endl;
    }
};

int main() {
    std::variant<int, double, std::string> v;
    v = 10;
    std::visit(PrintVisitor{}, v);
    v = 3.14;
    std::visit(PrintVisitor{}, v);
    v = "Hello, variant!";
    std::visit(PrintVisitor{}, v);

    return 0;
}

3.4 示例4------类型匹配2

c++ 复制代码
#include <iomanip>
#include <iostream>
#include <string>
#include <variant>
#include <vector>
 
// 要观览的 variant
using var_t = std::variant<int, long, double, std::string>;
 
// 1 的辅助常量
template<class> inline constexpr bool always_false_v = false;
 
// 2 的辅助类型
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
// 显式推导指引( C++20 起不需要)
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
 
int main() {
    std::vector<var_t> vec = {10, 15l, 1.5, "hello"};
    for(auto&& v: vec) {
        // 1 类型匹配观览器:亦能为带 4 个重载的 operator() 的类
        std::visit([](auto&& arg) {
            using T = std::decay_t<decltype(arg)>;
            if constexpr (std::is_same_v<T, int>)
                std::cout << "int with value " << arg << '\n';
            else if constexpr (std::is_same_v<T, long>)
                std::cout << "long with value " << arg << '\n';
            else if constexpr (std::is_same_v<T, double>)
                std::cout << "double with value " << arg << '\n';
            else if constexpr (std::is_same_v<T, std::string>)
                std::cout << "std::string with value " << std::quoted(arg) << '\n';
            else 
                static_assert(always_false_v<T>, "non-exhaustive visitor!");
        }, v);
    }
 
    for (auto&& v: vec) {
        // 2类型匹配观览器:有三个重载的 operator() 的类
        std::visit(overloaded {
            [](auto arg) { std::cout << arg << ' '; },
            [](double arg) { std::cout << std::fixed << arg << ' '; },
            [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
        }, v);
    }
}
  • output
sh 复制代码
int with value 10
long with value 15
double with value 1.5
std::string with value "hello"
10 15 1.500000 "hello" 

4. 结语

使用varirant+visit 能够避免多态使用里的不必要新类够建,与lambda结合能快速实现简洁且通用的代码,是一块功能强大的语法糖

本文由博客一文多发平台 OpenWrite 发布!

相关推荐
江畔柳前堤4 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore5 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考7 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX8 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面8 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号8 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽9 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI9 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者10 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者10 小时前
【征程6】校准量化中HistogramObserver解析
算法