C++訪問std::variant類型數(shù)據(jù)的幾種方式小結(jié)
前言
std::variant
(可變體) 是 C++17
中引入的一個(gè)新的類模板,提供了一種存儲(chǔ)不同類型的值的方式,類似于之前版本中的 union
(聯(lián)合體),但可以存儲(chǔ)非 POD
類型和類對(duì)象,能夠在運(yùn)行時(shí)進(jìn)行類型檢查和轉(zhuǎn)換,但具有更多的功能和更高的類型安全性,今天來看一下存儲(chǔ)在std::variant
中的數(shù)據(jù)要怎么讀取。
variant的簡單使用
可以參考cppreference網(wǎng)站的使用示例,也可以看看下面這個(gè)例子:
#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; }
在示例程序中,定義了一個(gè) std::variant
對(duì)象 value
來存儲(chǔ)整型、浮點(diǎn)型和字符串類型中的任意一種,然后分別將 value
賦值為整型、浮點(diǎn)型和字符串類型,并使用 std::get
來獲取對(duì)應(yīng)的值,此時(shí)可以正常打印 value
對(duì)象中存儲(chǔ)的值
當(dāng)我們?cè)噲D將 value 賦值為其它未在定義變量時(shí)指定的類型時(shí),編譯器將會(huì)報(bào)編譯錯(cuò)誤,而當(dāng)我我們?cè)噲D獲取 value 中不存在的類型的值時(shí),程序?qū)?huì)拋出 std::bad_variant_access 異常,可以使用 try-catch 已經(jīng)捕獲。
通過這段代碼我們可以得知,使用std::variant可以方便地存儲(chǔ)多種類型的數(shù)據(jù),并且能夠在運(yùn)行時(shí)進(jìn)行類型檢查和轉(zhuǎn)換,這使得代碼更加清晰易讀,便于維護(hù)。
variant相關(guān)函數(shù)和類
- 成員函數(shù)
index
:返回 variant 中保存用類型的索引下標(biāo)valueless_by_exception
:返回 variant 是否處于因異常導(dǎo)致的無值狀態(tài)emplace
:原位構(gòu)造 variant 中的值
- 非成員函數(shù)
visit
:通過調(diào)用variant保存類型值所提供的函數(shù)對(duì)象獲取具體值holds_alternative
:檢查某個(gè) variant 是否當(dāng)前持有某個(gè)給定類型std::get
:以給定索引或類型讀取 variant 的值,錯(cuò)誤時(shí)拋出異常get_if
:以給定索引或類型,獲得指向被指向的 variant 的值的指針,錯(cuò)誤時(shí)返回空指針
- 輔助類
monostate
:用作非可默認(rèn)構(gòu)造類型的 variant 的首個(gè)可選項(xiàng)的占位符類型(預(yù)防一些類型不提供無參構(gòu)造函數(shù))bad_variant_access
:非法地訪問 variant 的值時(shí)拋出的異常variant_npos
:非法狀態(tài)的 variant 的下標(biāo)
訪問std::variant數(shù)據(jù)
從前面提到的例子和函數(shù)說明,我們可以看到有多種方式來訪問std::variant數(shù)據(jù),接一下來一起總結(jié)一下:
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ù)獲取對(duì)應(yīng)值的指針,如果類型不匹配會(huì)返回空指針
std::visit函數(shù)
使用函數(shù)visit函數(shù)訪問時(shí),有點(diǎn)像使用std::sort
這類函數(shù),可以搭配自定義的結(jié)構(gòu)(排序)重寫operator()
,讓其變成可以被調(diào)用的函數(shù)對(duì)象,也可以定義lambda自帶可執(zhí)行特性。
自定義訪問結(jié)構(gòu)的寫法
#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; }
這種方式將多個(gè)lambda放到一起形成重載,進(jìn)而達(dá)到了訪問variant數(shù)據(jù)的目的。
overloaded是什么
上文例子中的最后一個(gè)中使用到了 overloaded
,這令人眼花繚亂的寫法著實(shí)很詭異,不過我們可以從頭來分析一下,最后兩個(gè)例子中等價(jià)的兩部分是
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(jià),我們首先的得弄清楚lambda表達(dá)式是什么,在剖析lambda之前先來看看 std::visit
函數(shù)需要的參數(shù)是什么,分析std::visit
的參數(shù),先看 struct VisitPackage
結(jié)構(gòu)更容易一些。
std::visit的第一個(gè)參數(shù)
通俗的來說std::visit
的第一個(gè)參數(shù)需要的是一個(gè)可執(zhí)行的對(duì)象,如果對(duì)象能被執(zhí)行就需要實(shí)現(xiàn) operator()
這個(gè)操作符,看起來像函數(shù)一樣,這就是為什么在 struct VisitPackage
中定義了 operator()
,并且定義了兩個(gè)形成了參數(shù)不同的靜態(tài)重載,作用就是為了在訪問 variant
對(duì)象時(shí)適配不同的類型,在訪問variant
對(duì)象時(shí)會(huì)選擇最匹配的 operator()
函數(shù),進(jìn)而實(shí)現(xiàn)了訪問variant中不同類型值行為不同的目的。
那lambda表達(dá)式能實(shí)現(xiàn)這個(gè)目的嗎?我們接著往下看
lambda 是什么
自從 C++11 引入lambda之后,對(duì)它贊美的聲音不絕于耳,那lambda表達(dá)式究竟是怎樣實(shí)現(xiàn)的呢?真的就是一個(gè)普通的函數(shù)嗎?我們看一個(gè)小例子:
int main() { int x = 5, y = 6; auto func = [&](int n) { return x + n; }; func(7); return 0; }
這是一個(gè)使用lambda表達(dá)式簡單的例子,代碼中定義了一個(gè)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)我們雖然定義了一個(gè)lambda函數(shù),但是編譯器為它生成了一個(gè)類 __lambda_4_15
,生成了 int&
類型的構(gòu)造函數(shù),并實(shí)現(xiàn)了 operator
操作符,再調(diào)用lambda函數(shù)時(shí)先生成了 __lambda_4_15
類的對(duì)象,再調(diào)用類的 operator()
函數(shù) func.operator()(7);
,看到這里是不是似曾相識(shí),雖然還不是很明白,但是和struct VisitPackage
的定義總是有種說不清道不明的血緣關(guān)系。
弄清楚了lambda函數(shù)的本質(zhì),現(xiàn)在要實(shí)現(xiàn)的是怎么把多個(gè)lambda函數(shù)合成一個(gè)對(duì)象,并且讓他們形成重載,因?yàn)閘ambda函數(shù)本質(zhì)上存在一個(gè)類,只需要定義一個(gè)子類,繼承多個(gè)lambda表達(dá)式就可以了,其實(shí) overloaded
這個(gè)模板類就是為了實(shí)現(xiàn)這個(gè)目的。
overloaded剖析
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; }; template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
一時(shí)間看起來很難理解,它來自 en.cppreference.com 中介紹 std::visit
訪問 std::variant
的例子,可以換行看得更清楚一點(diǎn):
// 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()…; };
這是一個(gè)類模板的聲明,模板的名字是overloaded
分步拆解來看:
template<class… Ts> struct overloaded
- 表示類的模板參數(shù)為可變長的參數(shù)包 Ts
- 假設(shè) Ts 包含 T1, T2, … , TN,那么這一句聲明可以展開為:template<class T1, class T2, …, class TN> struct overloaded
struct overloaded : Ts…
- 表示類的基類為參數(shù)包 Ts 內(nèi)所有的參數(shù)類型
- 假設(shè) Ts 包含 T1, T2, … , TN,那么這一句聲明可以展開為:struct overloaded : T1, T2, …, TN
{ using Ts::operator()…; };
- 這是一個(gè)函數(shù)體內(nèi)的變長 using 聲明
- 假設(shè) 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…>;
- 這是一個(gè)自動(dòng)推斷向?qū)дf明,用于幫助編譯器根據(jù) overloaded 構(gòu)造器參數(shù)的類型來推導(dǎo) overloaded 的模板參數(shù)類型,C++17需要,C++20已不必寫
- 它告訴編譯器,如果 overloaded 構(gòu)造器所有參數(shù)的類型的集合為Ts,那么 overloaded 的模板參數(shù)類型就是 Ts 所包含的所有類型
- 如果表達(dá)式a1, a2, …, an的類型分別為T1, T2, …, TN,那么構(gòu)造器表達(dá)式overloaded x{a1, a2, …, an} 推導(dǎo)出,overloaded的類型就是 overloaded<T1, T2, …, TN>
經(jīng)過這些解釋,我們可以認(rèn)為在最后一個(gè)例子中可能產(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; }
總結(jié)
std::variant
可以存儲(chǔ)多個(gè)類型的值,并且它會(huì)自動(dòng)處理類型轉(zhuǎn)換和內(nèi)存分配std::variant
可以存儲(chǔ)非POD
類型和類對(duì)象,能夠在運(yùn)行時(shí)進(jìn)行類型檢查和轉(zhuǎn)換,具有更高的類型安全性- 可以使用
std::visit
全局函數(shù)來訪問std::variant
中存儲(chǔ)的值,該函數(shù)根據(jù)存儲(chǔ)的值的類型自動(dòng)選擇調(diào)用哪個(gè)函數(shù)對(duì)象 - 可以使用
std::holds_alternative
函數(shù)來檢查變量中是否存儲(chǔ)了特定的類型 - 定義lambda函數(shù)時(shí),編譯器會(huì)為其生成一個(gè)類
到此這篇關(guān)于C++訪問std::variant類型數(shù)據(jù)的幾種方式小結(jié)的文章就介紹到這了,更多相關(guān)C++訪問std::variant內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言實(shí)現(xiàn)電影院選座管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)電影院選座管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12關(guān)于C++繼承你可能會(huì)忽視的點(diǎn)
繼承是面向?qū)ο笕筇匦灾?有些類與類之間存在特殊的關(guān)系,下面這篇文章主要給大家介紹了關(guān)于C++繼承你可能會(huì)忽視的點(diǎn),文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02C++ 數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)兩個(gè)棧實(shí)現(xiàn)一個(gè)隊(duì)列
這篇文章主要介紹了詳解C++ 數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)兩個(gè)棧實(shí)現(xiàn)一個(gè)隊(duì)列的相關(guān)資料,需要的朋友可以參考下2017-03-03Visual?Studio?2022?安裝低版本?.Net?Framework的圖文教程
這篇文章主要介紹了Visual?Studio?2022?如何安裝低版本的?.Net?Framework,首先打開?Visual?Studio?Installer?可以看到vs2022?只支持安裝4.6及以上的版本,那么該如何安裝4.6以下的版本,下面將詳細(xì)介紹,需要的朋友可以參考下2022-09-09C語言實(shí)現(xiàn)賓館管理系統(tǒng)課程設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)賓館管理系統(tǒng)課程設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03C++實(shí)現(xiàn)關(guān)系與關(guān)系矩陣的代碼詳解
這篇文章主要介紹了C++實(shí)現(xiàn)關(guān)系與關(guān)系矩陣,功能實(shí)現(xiàn)包括關(guān)系的矩陣表示,關(guān)系的性質(zhì)判斷及關(guān)系的合成,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04