泛形variant+visit

泛形variant+visit

1|01.引言

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

但如果在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|02. variant

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

2|12.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|22.2 简单示例

#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|03. variant+visit

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

3|13.1 示例1——多类型同名函数调用

#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
10 15 1.5 hello

3|23.2 示例2——返回值

#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|33.3 示例3——类型匹配1

  • 类型匹配 即可根据不同的类型做不同的执行
#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|43.4 示例4——类型匹配2

#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
int with value 10 long with value 15 double with value 1.5 std::string with value "hello" 10 15 1.500000 "hello"

4|04. 结语

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

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


__EOF__

本文作者InsiApple
本文链接https://www.cnblogs.com/InsiApple/p/18103716.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   InsiApple  阅读(129)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示