any optional variant
六月 20, 2022 [c++] #c++any optional variant
any optional variant
std::optional, std::any, 和 std::variant 有一个共同特点:他们都支持就地构造.另外的,std::variant 还支持访问者模式.
首先,我们要了解一下这3种数据类型的功能作用.
std::optional 是一种可能包含也可能不包含某一类型对象的类型. std::variant 是一种类型安全的联合体 std::any 是一种可以包含任意类型(指可复制类型)对象的类型
Construct in-place
什么是就地构造呢?以 std::optional<std::string> 为例来说明就是: 所谓就地构造,就是你可以直接使用 std::string 的构造参数来构造 std::optional<std::string>.
std::optional<std::string> opt1(std::in_place, "C++17");
std::variant 支持 std::visit 方法(即精典的设计模式:访问者).
std::visit 方法允许你对一个 std::variants 列表应用访问者模式,而相应的访问者必须是一个callable类型,所谓 callable 类型,是一种可以被调用的类型,通常是一个函数,一个函数对象或者一个 lambda 函数.简单起见,这里我仅使用 lambda 函数来举例说明.
#include <iostream>
#include <vector>
#include <typeinfo>
#include <type_traits>
#include <variant>
int main()
{
std::cout << std::endl;
std::vector<std::variant<char, long, float, int, double, long long>> // 1
vecVariant = { 5, '2', 5.4, 100ll, 2011l, 3.5f, 2017 };
// display each value
for (auto& v : vecVariant) {
std::visit([](auto&& arg) {std::cout << arg << " "; }, v); // 2
}
std::cout << std::endl;
// display each type
for (auto& v : vecVariant) {
std::visit([](auto&& arg) {std::cout << typeid(arg).name() << " "; }, v); // 3
}
std::cout << std::endl;
// get the sum
std::common_type<char, long, float, int, double, long long>::type res{}; // 4
std::cout << "typeid(res).name(): " << typeid(res).name() << std::endl;
for (auto& v : vecVariant) {
std::visit([&res](auto&& arg) {res += arg; }, v); // 5
}
std::cout << "res: " << res << std::endl;
// double each value
for (auto& v : vecVariant) {
std::visit([](auto&& arg) {arg *= 2; }, v); // 6
std::visit([](auto&& arg) {std::cout << arg << " "; }, v);
}
std::cout << std::endl;
return 0;
}
common_type函数
包含于<type_traits>当中。 作用是返回参数列表中的参数都可以转换成的类型(有些拗口)。
C++11中的用法为
syd::common_type<int,float,double>::type // 返回值为double
std::any
一个类型安全的容器,可以放置各种类型的数据。
#include <any>
#include <iostream>
int main()
{
std::cout << std::boolalpha; //将bool值用 "true" 和 "false"显示
std::any a; //定义一个空的any,即一个空的容器
//有两种方法来判断一个any是否是空的
std << cout << a.has_value() <<std::endl; // any是空的时,has_value 返回值为 false
std << cout << a.type().name() <<std::endl; //any 是空的时,has_value 返回值为 true
//几种创建any的方式
std::any b = 1; //b 为存了int类型的值的any
auto c = std::make_any<float>(5.0f); //c为存了float类型的any
std::any d(6.0); //d为存储了double类型的any
std << cout << b.has_value() <<std::endl; //true
std << cout << b.type().name() <<std::endl; //int
std << cout << c.has_value() <<std::endl; //true
std << cout << c.type().name() <<std::endl; //float
std << cout << d.has_value() <<std::endl; //true
std << cout << d.type().name() <<std::endl; //double
//更改any的值
a = 2; //直接重新赋值
auto e = c.emplace<float>(4.0f); //调用emplace函数,e为新生成的对象引用
//清空any的值
b.reset();
std << cout << b.has_value() <<std::endl; //false
std << cout << b.type().name() <<std::endl; //int
//使用any的值
try
{
auto f = std::any_cast<int>(a); //f为int类型,其值为2
std::cout << f <<std::endl; //2
}
catch(const std::bad_any_cast& e)
{
std::cout<< e.what()<<std::endl;
}
try
{
auto g = std::any_cast<float>(a); //抛出std::bad_any_cat 异常
std::cout << g <<std::endl; //该语句不会执行
}
catch(const std::bad_any_cast& e)
{
std::cout<< e.what()<<std::endl; //可能输出Bad any_cast
}
return 0;
std::variant
std::variant是类型安全的union
#include <variant>
#include <iostream>
union my_union
{
int i;
float f;
char c;
};
int main()
{
std::cout << std::boolalpha;
std::variant<int, float, char> variant;
//这里的variant等价于my_union
//在构造的时候,如果构造过程中抛出了异常,valueless_by_exception的返回值为true
std::cout<< variant.valueless_by_exception()<<std::endl; //false
{
variant = 12; // variant包含了int类型
int i = std::get<int>(variant); //使用std::get<T>可以获取所含有的值
try
{
auto f = std::get<float>(variant); //此时的值为int,所以想要获取float的时候就会抛出异常
}
catch (const std::bad_variant_access & exception)
{
std::cout << exception.what() << std::endl;
}
variant = 1.0f;
auto f = std::get<float>(variant);
std::cout << f << std::endl; //1.0
}
{
//还可以使用索引来获取对应的值
auto f = std::get<1>(variant);
try
{
auto i = std::get<0>(variant);
}
catch (const std::bad_variant_access & exception)
{
std::cout << exception.what() << std::endl;
}
variant = 1;
auto i = std::get<0>(variant);
std::cout<<i<<std::endl; //1
}
variant = 2.0f;
std::cout << variant.index() << std::endl; //1
variant = 2;
std::cout << variant.index() << std::endl; //0
return 0;
}
std::optional
该类型是用来表示一个值是不是存在的。std::optional有两个状态,即有值和无值。通常我们将std::optional用于函数的返回值,当函数执行成功了返回有值的状态,当函数执行失败了返回无值的状态。当std::optional有值时,它可以在使用bool值的地方转化为true,反之,转化为false。
#include <iostream>
#include <optional>
int main()
{
std::cout << std::boolalpha;
std::optional<int> op1; //表示一个不存在的值
std::optional<int> op2 = 1; //表示一个存在的int类型的值
std::optional<int> op3(2); //表示存在的一个int类型的值
std::optional<std::string> op4(std::in_place, 3, 'A'); //构建一个存有std::string类型的std::optional,
//调用std::string的构造函数,其值为"AAA"
std::cout << op1.has_value() << std::endl; //输出为false
if (!op1)
{
std::cout << "empty optional is false" << std::endl; //由于op1不存在值,所以在此转化为false
}
try
{
std::cout << op4.value() << std::endl; //获取std::optional 的值,如果不存在值,则抛出std::bad_optional_access异常
}
catch (std::bad_optional_access & e)
{
std::cout << e.what() << std::endl;
}
std::optional<int> op5;
std::cout<<op5.value_or(5)<<std::endl;
op2.reset();
std::cout << op2.has_value() << std::endl; //false
op2 = 3;
op2 = 4.0;
std::cout << op2.value() << std::endl; //4.0
op2.emplace<int>(5);
return 0;
}
使用重载的Lambdas来vistor 访问 variant
https://blog.csdn.net/janeqi1987/article/details/100568146
template<typename... Ts> struct overload : Ts... { using Ts::operator()...; };
// base types are deduced from passed arguments:
template<typename... Ts> overloaded(Ts...) -> overloaded<Ts...>;
std::variant<double, bool, std::string> var;
var = string("hello");
// var = 1.0;
std::visit(overloaded {
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
}, var);
overloads方式访问std::variant
引入的overload<> 变参模板类, 核心思路是创建一个overloaded对象, 然后从传入的多个lambda表达式继承他们的operator()操作符(Lambda表达式概念上就是提供了operator()操作符的函数对象), 这样我们就可以在std::visit()中利用lambda方便的访问对应的std::variant了.
当然, 以上代码抛开C++17的相关特性, 解释起来都费力, 所以我们下面从关联的C++17特性介绍一下实现细节.
Pack extension in using declarations
using其实早在C++11的时候就加入到标准了, 但variadic template参数展开支持using表达式, 是17才支持的特性, 像如下代码声明:
using Ts::operator()...;
借助C++17支持的using展开, 我们很容易就完成了各lambda表达式的operator()操作符的expose. 这样子类就具备了所有父类的operator()操作符, 与我们1.5中声明的那个vistor struct很接近了.
Custom template argument deduction rules(或者 user-defined template argument deduction rules)
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>; 这个其实是一个跟c++11/14的时候加入的返回值deduce非常像的概念, 通过 user-defined template argument decduction, 我们可以告诉compiler, 括号操作符需要展开成 overloaded, 这样我们实际使用的时候就直接
overloaded {
[](auto arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
}
这种使用形式完成了我们的overloaded对象构造. 相关使用代码简单易读.