原文
试想,用std::variant<A,B,C>
擦除了类型
,然后按数格串``序化
该变量
对象,这挺爽的,想序化A,B,C
中,任意一个类型
,只要赋值
给变量
就行,代码不变
,不用再写一份,这用起来确实挺爽.
但是,从数格串
,反序化
到变量
时怎么办,你怎么知道该数格串
是A还是B还是C
呢?
然而,这就是社区
同学对iguana
提的一个需求,想序化和反序化
都支持变量
.
从数格串
,反序化到变量
确实是个问题,但不是个大问题
,可这样做:
试转换数格串
为变量
中的每一个类型
,直到成功为止
,成功
后把值赋给变量
即可.
这样就需要一个遍历变量
类型的功能了,同时还要注意,如果转换
失败了,需要恢复数格串
,再试下一次.看看iguana
怎么序化和反序化变量
的吧.
namespace my_space {
struct inner_struct {
int x;
int y;
int z;
};
REFLECTION(inner_struct, x, y, z);
} //my_space名字空间
struct nest_t {
std::string name;
my_space::inner_struct value;
std::variant<int, std::string, double> var;
std::variant<int, std::string, my_space::inner_struct> var2;
};
REFLECTION(nest_t, name, value, var, var2);
TEST_CASE("test variant") {
std::variant<int, std::string, double> var;
var = "test";
nest_t v{"Hi", {1, 2, 3}, var, my_space::inner_struct{2, 4, 6}};
std::string s;
iguana::to_json(v, s);
std::cout << s << std::endl;
nest_t v2;
iguana::from_json(v2, s);
CHECK(v.var == v2.var);
}
测试表明iguana
能正确反序化变量
类型.接着具体来看看iguana
是怎么序化和反序化变量
的吧.
序化
很简单,使用std::visit
就行了:
template <bool Is_writing_escape, typename Stream, typename T, std::enable_if_t<variant_v<T>, int>>
IGUANA_INLINE void to_json_impl(Stream &s, T &&t) {
std::visit(
[&s](auto value) {
to_json_impl<Is_writing_escape>(s, value);
},
t);
}
反序化
就比较复杂了:
先取variant_size
,然后根据该大小
生成编译期索引列表
,后面
再根据std::get(variant)
得到索引位置类型
,然后试转换数格串
到该类型
,编译期索引列表
用来遍历变量
的所有类型
.
template <size_t Idx, typename T>
using variant_element_t = std::remove_reference_t<decltype(std::get<Idx>(
std::declval<std::remove_reference_t<T>>()))>; //#2
template <typename U, typename It, size_t... Idx>
IGUANA_INLINE void from_json_variant(U &value, It &it, It &end, std::index_sequence<Idx...>) {
bool r = false;
It temp_it = it;
It temp_end = end;
((void)(!r && (r = from_json_variant_impl<
variant_element_t<Idx, std::remove_reference_t<U>>>(
value, it, end, temp_it, temp_end), true)),
...); //#3
it = temp_it;
end = temp_end;
}
template <typename U, typename It, std::enable_if_t<variant_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
from_json_variant(value, it, end, std::make_index_sequence< std::variant_size_v<std::remove_reference_t<U>>>{}); //#1
}
#1
代码根据variant_size
生成了编译期的索引序列
,#2
代码,根据编译期
索引取对应类型
,注意移除引用
,因为std::get(variant)
返回的是引用类型
.
#3
代码根据折叠式
来把每个类型
传入到from_json_variant_impl
函数中,其实现:
template <typename value_type, typename U, typename It>
IGUANA_INLINE bool from_json_variant_impl(U &value, It it, It end, It &temp_it, It &temp_end) {
try {
value_type val;
from_json_impl(val, it, end); //#4
value = val;
temp_it = it;
temp_end = end;
return true;
} catch (std::exception &ex) {
return false;
}
}
#4
代码会试转换数格串
为变量
的某个类型,如果转换失败
则会抛异常
,则返回假
,成功返回真
,为何返回bool
呢,因为需要保证转换成功
后,后面不会重入到from_json_variant_impl
函数中,具体是如何保证
的呢?
再回过头来看#3
代码.
bool r = false;
((void)(!r && (r = from_json_variant_impl<
variant_element_t<Idx, std::remove_reference_t<U>>>(
value, it, end, temp_it, temp_end),
true)),
...);
最开始控制变量为假
,第一次肯定会进入到from_json_variant_impl
,如果from_json_variant_impl
返回真
,则逻辑与式
的左边结果
为假
,则整个式一定为假
,下次就不会再进入
到from_json_variant_impl
函数中了;
如果from_json_variant_impl
返回假
,则逻辑与式
左边为真
,此时还需要依赖右边
的式结果
才能知道是否
为真,因此会继续进入
到from_json_variant_impl
函数中.
另外一个方式是,把bool
变量r
按from_json_variant_impl
函数的出参
,检查
函数开头,如果发现它,按真
则不执行后面的代码
,这样才能解决问题
,但是会多次进入函数
.
利用bool
控制变量和逗号式
,可很好控制
是否需要进入
到from_json_variant_impl
函数中,性能也会更好,虽然代码复杂
了一些,但这已是性能
最好了.