本文为 《C++17 in detail》 一书的中文渣中渣译文,不足之处还望指正。
翻译太特么累人了……剩余部分还是只做摘要翻译吧。
7. std::variant variant 是一种类型安全的 union。
头文件:
综合应用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <string> #include <iostream> #include <variant> using namespace std ;struct PrintVisitor { void operator () (int i) { cout << "int: " << i << '\n' ; } void operator () (float f) { cout << "float: " << f << '\n' ; } void operator () (const string & s) { cout << "str: " << s << '\n' ; } }; int main () { variant<int , float , string > intFloatString; static_assert (variant_size_v<decltype (intFloatString)> == 3 ); visit(PrintVisitor{}, intFloatString); cout << "index = " << intFloatString.index() << endl ; intFloatString = 100.0f ; cout << "index = " << intFloatString.index() << endl ; intFloatString = "hello super world" ; cout << "index = " << intFloatString.index() << endl ; if (const auto intPtr = get_if<int >(&intFloatString)) cout << "int: " << *intPtr << '\n' ; else if (const auto floatPtr = get_if<float >(&intFloatString)) cout << "float: " << *floatPtr << '\n' ; if (holds_alternative<int >(intFloatString)) cout << "the variant holds an int!\n" ; else if (holds_alternative<float >(intFloatString)) cout << "the variant holds a float\n" ; else if (holds_alternative<string >(intFloatString)) cout << "the variant holds a string\n" ; try { auto f = get <float >(intFloatString); cout << "float! " << f << '\n' ; } catch (bad_variant_access&) { cout << "our variant doesn't hold float at this moment...\n" ; } }
输出:
1 2 3 4 5 6 int : 0 index = 0 index = 1 index = 2 the variant holds a string our variant doesn' t hold float at this moment...
示例有几个点值得注意:
如果没有进行值初始化,variant 使用第一个类型初始化。此时,第一个类型必须存在默认构造函数。
index() 返回当前使用的类型的索引;bool holds_alternative 函数检查当前使用类型是否是 Type。
get<Index>
、get<Type>
返回当前索引/使用类型的值,如果当前使用类型和 Index/Type 不匹配,会抛出 bad_variant_access 异常。
get_if<Index>
、get_if<Type>
返回当前索引/使用类型的变量的指针,如果当前使用类型和 Index/Type 不匹配,返回 nullptr。
可以配合 std::visit 使用。
7.1 创建
std::in_place_index、std::in_place_type 显式赋值
1 2 variant<vector <int >, string > v{ std ::in_place_index<0 >, { 0 , 1 , 2 , 3 } }; variant<vector <int >, string > v{ std ::in_place_type<int >, { 0 , 1 , 2 , 3 } };
显式指定当前索引/类型,并使用后续参数进行原地构造。
std::monostate 选项
1 variant<monostate, Type-Without-Default-Constructor> v;
monostate 是 STL 提供的一个辅助类,通常用于 variant 第一个类型,可以解决第一个选项没有默认构造函数导致 variant 变量不能默认构造的问题。
monostate 可以出现在任何位置,也可以存在多个,但是违背本意。
7.2 修改
emplace<Index>
、emplace<Type>
,支持基于索引/类型的原地构造。
7.3 访问存储值
get、get_if 都不是 variant 的成员函数
variant 上的访问者(visitor)
同 std::variant 一起引入的还有一个好用的 STL 函数:std::visit。
它可以对所有传入的 variant 调用一个 “访问者”。
声明如下:
1 2 template <class Visitor , class ... Variants >constexpr visit (Visitor && vis , Variant &&... vars );
visit 会对 variant 当前使用类型调用 vis。
如果只传入一个 variant,你必须重载 variant 里所有类型(的 operator())。如果传入两个 variant,你必须重载这两个 variant 所有可能的类型组合。
访问者是“一个可调用对象,它接受每一个 variant 里每一种可能的选项”。
泛型 lambda 实现的访问者示例:
1 2 3 4 5 auto PrintVisitor = [](const auto & t) { std ::cout << t << '\n' ; };std ::variant<int , float , std ::string > intFloatString { "Hello" };std ::visit(PrintVisitor, intFloatString);
访问者同样可以修改 variant 的值,只需将访问者参数改为非常量引用。
大多数情况下,我们会希望对不同类型执行不同的动作,这时候就需要重载 operator()。
2.1 overload 模板类实现
1 2 3 4 template <class ... Ts > struct overload : Ts... { using Ts::operator ()...; };template <class ... Ts > overload (Ts ...) -> overload <Ts...>;
2.2 std::visit 中使用 overload
1 2 3 4 5 6 7 8 9 std ::variant<int , float , std ::string > myVariant;std ::visit( overload { [](const int & i) { std ::cout << "int: " << i; }, [](const std ::string & s) { std ::cout << "string: " << s; }, [](const float & f) { std ::cout << "float: " << f; } }, myVariant );
访问多个 variant
std::visit 允许传入多个 variant,表现是依序分别将每个 variant 中的当前类型作为访问者函数的参数。这就要求访问者必须能处理所有可能的类型组合。
比如:
1 2 std ::variant<int , float , char > v1 { 's' };std ::variant<int , float , char > v2 { 10 };
可以通过提供 9 个重载实现访问者:
1 2 3 4 5 6 7 8 9 10 11 std ::visit(overload{ [](int a, int b) { }, [](int a, float b) { }, [](int a, char b) { }, [](float a, int b) { }, [](float a, float b) { }, [](float a, char b) { }, [](char a, int b) { }, [](char a, float b) { }, [](char a, char b) { } }, v1, v2);
可以添加泛型 lambda 版本的重载批量处理一些无效组合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 std ::variant<Pizza, Chocolate, Salami, IceCream> firstIngredient { IceCream() };std ::variant<Pizza, Chocolate, Salami, IceCream> secondIngredient { Chocolate()};std ::visit(overload{ [](const Pizza& p, const Salami& s) { std ::cout << "here you have, Pizza with Salami!\n" ; }, [](const Salami& s, const Pizza& p) { std ::cout << "here you have, Pizza with Salami!\n" ; }, [](const Chocolate& c, const IceCream& i) { std ::cout << "Chocolate with IceCream!\n" ; }, [](const IceCream& i, const Chocolate& c) { std ::cout << "IceCream with a bit of Chocolate!\n" ; }, [](const auto & a, const auto & b) { std ::cout << "invalid composition...\n" ; }, }, firstIngredient, secondIngredient);
7.4 其它操作
比较
如果当前使用类型相同,使用相应类型的比较操作符比较;
如果当前类型不相同,以 index() 进行比较。
variant 是值类型,可以移动。
可以对 std::variant 执行 std::hash。
7.5 性能 & 内存考量
同 optional 一样,variant 以尺寸最大的选项确定自身占用空间,另外有一个标识当前索引的变量。因为总是相邻存在,这个额外的标识有可能影响 CPU cache。
同 optional 一样,variant 不会动态分配内存。