泛形variant+visit
1.引言
在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. 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 簡單示例
#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——多型別同名函式呼叫
#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.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.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.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. 結語
使用varirant+visit
能夠避免多型使用裡的不必要新類夠建,與lambda結合能快速實現簡潔且通用的程式碼,是一塊功能強大的語法糖
本文由部落格一文多發平臺 OpenWrite 釋出!