C++訪問std::variant類型數(shù)據(jù)的幾種方式小結
前言
std::variant
(可變體) 是 C++17
中引入的一個新的類模板,提供了一種存儲不同類型的值的方式,類似于之前版本中的 union
(聯(lián)合體),但可以存儲非 POD
類型和類對象,能夠在運行時進行類型檢查和轉換,但具有更多的功能和更高的類型安全性,今天來看一下存儲在std::variant
中的數(shù)據(jù)要怎么讀取。
variant的簡單使用
可以參考cppreference網(wǎng)站的使用示例,也可以看看下面這個例子:
#include <iostream> #include <variant> #include <string> int main() { std::variant<int, double, std::string> value; value = 110; std::cout << "The value is an integer: " << std::get<int>(value) << std::endl; value = 0.618; std::cout << "The value is a double: " << std::get<double>(value) << std::endl; value = "hello world"; std::cout << "The value is a string: " << std::get<std::string>(value) << std::endl; // value = true; // Compilation error: cannot convert type bool to any of the alternative types // std::get<int>(value) // std::bad_variant_access exception: value holds a different type return 0; }
在示例程序中,定義了一個 std::variant
對象 value
來存儲整型、浮點型和字符串類型中的任意一種,然后分別將 value
賦值為整型、浮點型和字符串類型,并使用 std::get
來獲取對應的值,此時可以正常打印 value
對象中存儲的值
當我們試圖將 value 賦值為其它未在定義變量時指定的類型時,編譯器將會報編譯錯誤,而當我我們試圖獲取 value 中不存在的類型的值時,程序將會拋出 std::bad_variant_access 異常,可以使用 try-catch 已經(jīng)捕獲。
通過這段代碼我們可以得知,使用std::variant可以方便地存儲多種類型的數(shù)據(jù),并且能夠在運行時進行類型檢查和轉換,這使得代碼更加清晰易讀,便于維護。
variant相關函數(shù)和類
- 成員函數(shù)
index
:返回 variant 中保存用類型的索引下標valueless_by_exception
:返回 variant 是否處于因異常導致的無值狀態(tài)emplace
:原位構造 variant 中的值
- 非成員函數(shù)
visit
:通過調用variant保存類型值所提供的函數(shù)對象獲取具體值holds_alternative
:檢查某個 variant 是否當前持有某個給定類型std::get
:以給定索引或類型讀取 variant 的值,錯誤時拋出異常get_if
:以給定索引或類型,獲得指向被指向的 variant 的值的指針,錯誤時返回空指針
- 輔助類
monostate
:用作非可默認構造類型的 variant 的首個可選項的占位符類型(預防一些類型不提供無參構造函數(shù))bad_variant_access
:非法地訪問 variant 的值時拋出的異常variant_npos
:非法狀態(tài)的 variant 的下標
訪問std::variant數(shù)據(jù)
從前面提到的例子和函數(shù)說明,我們可以看到有多種方式來訪問std::variant數(shù)據(jù),接一下來一起總結一下:
std::get搭配index函數(shù)使用
#include <iostream> #include <variant> int main() { std::variant<double, int> value = 119; if (1 == value.index()) std::cout << "The value is: " << std::get<1>(value) << std::endl; return 0; }
先用 index()
查詢 variant保存的類型索引,然后在通過 std::get<NUMBER>()
獲取其中的值
std::get搭配std::holds_alternative函數(shù)使用
#include <iostream> #include <variant> int main() { std::variant<double, int> value = 119; if (std::holds_alternative<int>(value)) std::cout << "The value is: " << std::get<int>(value) << std::endl; return 0; }
先通過 std::holds_alternative()
查詢 variant保存的類型,然后在通過 std::get<TYPE>()
獲取其中的值
std::get_if函數(shù)
#include <iostream> #include <variant> int main() { std::variant<double, int> value = 119; if (auto p = std::get_if<int>(&value)) std::cout << "The value is: " << *p << std::endl; return 0; }
直接使用 std::get_if
函數(shù)獲取對應值的指針,如果類型不匹配會返回空指針
std::visit函數(shù)
使用函數(shù)visit函數(shù)訪問時,有點像使用std::sort
這類函數(shù),可以搭配自定義的結構(排序)重寫operator()
,讓其變成可以被調用的函數(shù)對象,也可以定義lambda自帶可執(zhí)行特性。
自定義訪問結構的寫法
#include <iostream> #include <variant> int main() { std::variant<double, int> value = 119; struct VisitPackage { auto operator()(const double& v) { std::cout << "The value is: " << v << std::endl; } auto operator()(const int& v) { std::cout << "The value is: " << v << std::endl; } }; std::visit(VisitPackage(), value); return 0; }
定義lambda函數(shù)組重載
#include <iostream> #include <variant> template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; }; template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>; int main() { std::variant<double, int> value = 119; std::visit(overloaded { [] (const double& v) { std::cout << "The value is: " << v << std::endl; }, [] (const int& v) { std::cout << "The value is: " << v << std::endl; } }, value); return 0; }
這種方式將多個lambda放到一起形成重載,進而達到了訪問variant數(shù)據(jù)的目的。
overloaded是什么
上文例子中的最后一個中使用到了 overloaded
,這令人眼花繚亂的寫法著實很詭異,不過我們可以從頭來分析一下,最后兩個例子中等價的兩部分是
struct VisitPackage { auto operator()(const double& v) { std::cout << "The value is: " << v << std::endl; } auto operator()(const int& v) { std::cout << "The value is: " << v << std::endl; } }; VisitPackage()
與
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; }; template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>; overloaded { [] (const double& v) { std::cout << "The value is: " << v << std::endl; }, [] (const int& v) { std::cout << "The value is: " << v << std::endl; } }
要想理解它們?yōu)槭裁吹葍r,我們首先的得弄清楚lambda表達式是什么,在剖析lambda之前先來看看 std::visit
函數(shù)需要的參數(shù)是什么,分析std::visit
的參數(shù),先看 struct VisitPackage
結構更容易一些。
std::visit的第一個參數(shù)
通俗的來說std::visit
的第一個參數(shù)需要的是一個可執(zhí)行的對象,如果對象能被執(zhí)行就需要實現(xiàn) operator()
這個操作符,看起來像函數(shù)一樣,這就是為什么在 struct VisitPackage
中定義了 operator()
,并且定義了兩個形成了參數(shù)不同的靜態(tài)重載,作用就是為了在訪問 variant
對象時適配不同的類型,在訪問variant
對象時會選擇最匹配的 operator()
函數(shù),進而實現(xiàn)了訪問variant中不同類型值行為不同的目的。
那lambda表達式能實現(xiàn)這個目的嗎?我們接著往下看
lambda 是什么
自從 C++11 引入lambda之后,對它贊美的聲音不絕于耳,那lambda表達式究竟是怎樣實現(xiàn)的呢?真的就是一個普通的函數(shù)嗎?我們看一個小例子:
int main() { int x = 5, y = 6; auto func = [&](int n) { return x + n; }; func(7); return 0; }
這是一個使用lambda表達式簡單的例子,代碼中定義了一個int類型參數(shù)的返回值也是int的lambda函數(shù),作用就是將外部變量x與函數(shù)參數(shù)的和返回,我們使用 cppinsights.io 網(wǎng)站來將此段代碼展開
int main() { int x = 5; int y = 6; class __lambda_4_15 { public: inline /*constexpr */ int operator()(int n) const { return x + n; } private: int & x; public: __lambda_4_15(int & _x) : x{_x} {} }; __lambda_4_15 func = __lambda_4_15{x}; func.operator()(7); return 0; }
可以發(fā)現(xiàn)我們雖然定義了一個lambda函數(shù),但是編譯器為它生成了一個類 __lambda_4_15
,生成了 int&
類型的構造函數(shù),并實現(xiàn)了 operator
操作符,再調用lambda函數(shù)時先生成了 __lambda_4_15
類的對象,再調用類的 operator()
函數(shù) func.operator()(7);
,看到這里是不是似曾相識,雖然還不是很明白,但是和struct VisitPackage
的定義總是有種說不清道不明的血緣關系。
弄清楚了lambda函數(shù)的本質,現(xiàn)在要實現(xiàn)的是怎么把多個lambda函數(shù)合成一個對象,并且讓他們形成重載,因為lambda函數(shù)本質上存在一個類,只需要定義一個子類,繼承多個lambda表達式就可以了,其實 overloaded
這個模板類就是為了實現(xiàn)這個目的。
overloaded剖析
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; }; template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
一時間看起來很難理解,它來自 en.cppreference.com 中介紹 std::visit
訪問 std::variant
的例子,可以換行看得更清楚一點:
// helper type for the visitor #4 template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; }; // explicit deduction guide (not needed as of C++20) template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template<class… Ts> struct overloaded : Ts… { using Ts::operator()…; };
這是一個類模板的聲明,模板的名字是overloaded
分步拆解來看:
template<class… Ts> struct overloaded
- 表示類的模板參數(shù)為可變長的參數(shù)包 Ts
- 假設 Ts 包含 T1, T2, … , TN,那么這一句聲明可以展開為:template<class T1, class T2, …, class TN> struct overloaded
struct overloaded : Ts…
- 表示類的基類為參數(shù)包 Ts 內(nèi)所有的參數(shù)類型
- 假設 Ts 包含 T1, T2, … , TN,那么這一句聲明可以展開為:struct overloaded : T1, T2, …, TN
{ using Ts::operator()…; };
- 這是一個函數(shù)體內(nèi)的變長 using 聲明
- 假設 Ts 包含 T1, T2, … , TN,那么這一句聲明可以展開為:{ using T1::operator(), T1::operator(), …, TN::operator(); }
- 經(jīng)過這步聲明,overloaded 類的參數(shù)包 Ts 內(nèi)所有的參數(shù)作為基類的成員函數(shù)operator()均被 overloaded 類引入了自己的作用域
template<class… Ts> overloaded(Ts…) -> overloaded<Ts…>;
- 這是一個自動推斷向導說明,用于幫助編譯器根據(jù) overloaded 構造器參數(shù)的類型來推導 overloaded 的模板參數(shù)類型,C++17需要,C++20已不必寫
- 它告訴編譯器,如果 overloaded 構造器所有參數(shù)的類型的集合為Ts,那么 overloaded 的模板參數(shù)類型就是 Ts 所包含的所有類型
- 如果表達式a1, a2, …, an的類型分別為T1, T2, …, TN,那么構造器表達式overloaded x{a1, a2, …, an} 推導出,overloaded的類型就是 overloaded<T1, T2, …, TN>
經(jīng)過這些解釋,我們可以認為在最后一個例子中可能產(chǎn)生了類似這樣的代碼:
#include <iostream> #include <variant> class __lambda_12_7 { public: inline /*constexpr */ void operator()(const double & v) const { std::operator<<(std::cout, "The value is: ").operator<<(v).operator<<(std::endl); } }; class __lambda_13_7 { public: inline /*constexpr */ void operator()(const int & v) const { std::operator<<(std::cout, "The value is: ").operator<<(v).operator<<(std::endl); } }; template<> struct overloaded<__lambda_12_7, __lambda_13_7> : public __lambda_12_7, public __lambda_13_7 { using __lambda_12_7::operator(); // inline /*constexpr */ void ::operator()(const double & v) const; using __lambda_13_7::operator(); // inline /*constexpr */ void ::operator()(const int & v) const; }; int main() { std::variant<double, int> value = std::variant<double, int>(119); std::visit(overloaded{__lambda_12_7(__lambda_12_7{}), __lambda_13_7(__lambda_13_7{})}, value); return 0; }
總結
std::variant
可以存儲多個類型的值,并且它會自動處理類型轉換和內(nèi)存分配std::variant
可以存儲非POD
類型和類對象,能夠在運行時進行類型檢查和轉換,具有更高的類型安全性- 可以使用
std::visit
全局函數(shù)來訪問std::variant
中存儲的值,該函數(shù)根據(jù)存儲的值的類型自動選擇調用哪個函數(shù)對象 - 可以使用
std::holds_alternative
函數(shù)來檢查變量中是否存儲了特定的類型 - 定義lambda函數(shù)時,編譯器會為其生成一個類
到此這篇關于C++訪問std::variant類型數(shù)據(jù)的幾種方式小結的文章就介紹到這了,更多相關C++訪問std::variant內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++ 數(shù)據(jù)結構實現(xiàn)兩個棧實現(xiàn)一個隊列
這篇文章主要介紹了詳解C++ 數(shù)據(jù)結構實現(xiàn)兩個棧實現(xiàn)一個隊列的相關資料,需要的朋友可以參考下2017-03-03Visual?Studio?2022?安裝低版本?.Net?Framework的圖文教程
這篇文章主要介紹了Visual?Studio?2022?如何安裝低版本的?.Net?Framework,首先打開?Visual?Studio?Installer?可以看到vs2022?只支持安裝4.6及以上的版本,那么該如何安裝4.6以下的版本,下面將詳細介紹,需要的朋友可以參考下2022-09-09