變體類型 std::variant
在此之前需要先介紹一下類模板std::variant
,其表示一個類型安全的聯合體。 std::variant
的一個實例在任意時刻要么保有其一個可選類型之一的值,要么在錯誤情況下無值。與union
在聚合初始化中的行為一致, 若 variant 保有某個對象類型 T 的值,則直接於 variant 的對象表示中分配 T 的對象表示。不允許 variant 分配額外的(動態)內存。簡單來說就是一個可變類型的變量,其獲取方法跟元組的第一個獲取方法類似。代碼如下
#include <variant>
#include <string>
#include <cassert>
int main()
{
std::variant<int, float> v, w;
v = 12; // v 含 int
int i = std::get<int>(v);
w = std::get<int>(v);
w = std::get<0>(v); // 與前一行效果相同
}
更加優秀的狀態是std::visit
和overloaded
模板函數相互配合實現,代碼如下:
#include <variant>
#include <iostream>
#include <sstream>
#include <string>
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
int main() {
std::stringstream convert;
std::variant<int, long long int, std::string, double> value = 12.3;
std::visit(overloaded{
[&convert](int value) { convert << value << 'i'; },
[&convert](long long int value) { convert << value << 'i'; },
[&convert](double value) { convert << value; },
[&convert](const std::string &value) { convert << '"' << value << '"'; },
}, value);
std::cout << convert.str();
}
如果看懂了,你肯定感覺很驚艷。不過作為一個入門級選手的我,屬實看不懂。首先是overload
就沒看懂,不錯是template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
是模板參數的自動推導
,如果你知道這是類型推導的一步,那已經明白1/3了。
[&convert](int value) { convert << value << 'i'; },
[&convert](long long int value) { convert << value << 'i'; },
[&convert](double value) { convert << value; },
[&convert](const std::string &value) { convert << '"' << value << '"'; }
這四句話都是如果不明白Lambda 表達式
可以查看博文C++ 11 新特性總結,而Lambda 表達式
是一個函數對象,其作為overload
參數輸入,會使得編譯器根據自動推導命令,直接將其類作為模板參數表實現類型的聲明即獲得一個類overloaded<Lambda1,Lambda2,Lambda3>
。
接下來是using的變長聲明
,using 聲明
還挺長一段,詳細內容可以查看C++ 中文指南,簡單來說,就是直接將父類中的函數直接拿來用,效果就像不是繼承而來,在類內自己實現的。這樣就有一個效果,如果父類和子類具有同名函數,便形成了函數重載。所以這里的using Ts::operator()...
是將所有根據Lambda表達式所獲取的繼承類Ts
的()
運算符函數直接拿來用,並且由於函數名一樣出現類似函數重載的效果。
最后是std::visit
函數,其聲明如下:
template <class Visitor, class... Variants>
constexpr /*...*/ visit(Visitor&& vis, Variants&&... vars);
其實現的功能是,將vars
的可能值傳入vis
中。
所以最終便會實現,根據variant
中可用參數類型不同,最終生成不一樣的字符串,傳入字符串流對象convert
中。
到此字典類型核心便的實現了一般,下一步是字符串映射,使用C++標准庫中的std::map
或std::unordered_map
便可以實現了。下面是示例代碼,可見可以輕松實現Python的字典效果。
#include <variant>
#include <iostream>
#include <sstream>
#include <string>
#include <unordered_map>
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
int main() {
std::variant<int, long int, char, std::string, float, double> value1;
std::unordered_map<std::string, std::variant<int, long int, char, std::string, float, double>> dictionary;
dictionary["a"] = "Hello";
dictionary["b"] = 'I';
dictionary["c"] = 2;
dictionary["d"] = 3.14159f;
std::stringstream convert;
for (const auto &item : dictionary) {
convert.clear();
convert.str("");
std::visit(overloaded{
[&convert](char value) { convert << value; },
[&convert](int value) { convert << value; },
[&convert](long int value) { convert << value; },
[&convert](float value) { convert << value; },
[&convert](double value) { convert << value; },
[&convert](const std::string &value) { convert << value; },
}, item.second);
std::cout << item.first << ' ' << convert.str() << '\n';
}
}
打印信息如下:
d 3.14159
c 2
a Hello
b I