泛形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 特点
- 类型安全 :
std::variant
确保在编译时检查类型,因此可以避免运行时的类型错误。 - 多态值 :
std::variant
可以存储不同的数据类型,这使得它非常灵活,可以在一种类型安全的情况下处理多态值。 - 访问备选项 :使用
std::get<>()
或std::get_if<>()
可以访问std::variant
中存储的备选项。此外,std::visit()
函数提供了一种通用的方式来访问std::variant
中的值,类似于多态行为。 - 异常安全 :与使用裸指针和类型转换相比,
std::variant
提供了更好的异常安全性,因为它保证只能存储其所允许的类型。 - 替代方案 :在以前的 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 发布!