- rttr/detail/variant/variant_data_policy.h
- rttr/detail/variant/variant_compare.h
- rttr/detail/variant/variant_compare.cpp
- rttr/detail/comparison/*
剩余的policy的处理都大同小异,rttr将其中公共部分提取出来,实现了一个variant_data_policy_base类,下面的代码主要展示compare的操作。
template<typename T, typename Tp, typename Converter>
struct variant_data_base_policy
{
static bool invoke(variant_policy_operation op,
const variant_data& src_data, argument_wrapper arg)
{
switch (op)
{
case variant_policy_operation::CLONE:
{
Tp::clone(Tp::get_value(src_data), arg.get_value<variant_data>());
break;
}
...
// 这里与之前差不多,都是通过tuple然后再通过
// argument_wrapper来传递和返回参数
case variant_policy_operation::COMPARE_EQUAL:
{
const auto& param = arg.get_value<std::tuple<const variant&,
const variant&, bool&>>();
const variant& lhs = std::get<0>(param);
const variant& rhs = std::get<1>(param);
bool& ok = std::get<2>(param);
const type rhs_type = rhs.get_type();
const type lhs_type = type::get<T>();
const auto is_lhs_arithmetic = std::is_arithmetic<T>::value;
// 这里调用compare_equal来对比类型是否一样,这里传入的类型不固定,
// 通过偏特化模板对不同的类型采用不同的对比方法
if (lhs_type == rhs_type)
{
return compare_equal(src_data, rhs, ok);
}
else
{
// 这里对算术数值类型做比较处理
if (is_lhs_arithmetic && rhs_type.is_arithmetic())
{
return variant_compare_equal(lhs, lhs_type, rhs, rhs_type, ok);
}
// 如果不是算术类型,需要尝试将rhs转成lhs的类型,并通过lhs的类型取值
// 再与src_data的值进行比较。
else
{
variant var_tmp;
if (rhs.convert(lhs_type, var_tmp))
return compare_equal(src_data, var_tmp, ok);
else if (lhs.convert(rhs_type, var_tmp))
return (var_tmp.compare_equal(rhs, ok));
// 如果rhs是nullptr的话,只要判断src_data是否nullptr即可
else if (rhs.is_nullptr())
return is_nullptr(Tp::get_value(src_data));
}
}
return false;
}
case variant_policy_operation::COMPARE_LESS:
{
...
// 这里的比较和上面COMPARE_EQUAL差不多
if (lhs_type == rhs_type)
{
// 类型相同的时候使用 compare_less_than 判断并且返回结果
// 如果比较失败放在后面处理
if ((ok = compare_less_than(src_data, rhs, result)) == true)
return (result == -1 ? true : false);
}
else
{
// 类型不想同时的比较
return variant_compare_less(lhs, lhs_type, rhs, rhs_type, ok);
}
// 类型相同时如果比较失败,会尝试把lhs和rhs转换成字符串,
// 按照字符串大小进行比较,只有字符串转换成功,
// 并且lhs < rhs才返回true
bool ok1 = false;
bool ok2 = false;
auto ret = (lhs.to_string(&ok1) < rhs.to_string(&ok2));
if (ok1 && ok2)
{
ok = true;
return ret;
}
else
{
return false;
}
}
...
}
}
}
一部分非公共部分的处理通过调用Tp::xxx来实现,Tp类型就是真正的variant policy,variant policy通过继承variant_data_base_policy,并且把类型通过模板传给基类,实现基类和派生类静态函数的互相调用。 比如上面的get_value,invoke函数在派生类中不会被覆盖,因此调用基类和派生类的invoke都会调用这里来,但是get_value的处理方式不一样,所以需要调用派生类的get_value来获取值。
下面对代码中compare_equal相关的函数做说明,这里主要比较lhs和rhs里实际的数据是否相等,比较相等的部分主要用了 compare_equal 和 variant_compare_equal 这两个函数,下面代码是这两个函数的实现和说明
// 判断给出的类型,返回一个bool的constexpr表示是否符合以下的类型
// 这些类型是可以进行大小表的
template<typename T>
using is_comparable_type =
std::integral_constant<bool, std::is_same<T, std::string>::value ||
std::is_same<T, string_view>::value ||
std::is_arithmetic<T>::value ||
std::is_enum<T>::value ||
std::is_same<T, std::nullptr_t>::value ||
std::is_pointer<T>::value
>;
template<typename T>shih
using has_equal_operator_impl =
std::integral_constant<bool, has_equal_operator<T>::value &&
!std::is_array<T>::value &&
!is_template_instance<T>::value
>;
// 判断类型是否可以比较,判定的条件是是否有比较运算符,或者是否是可比较的类型
template<typename T>
using is_equal_comparable = std::integral_constant<bool,
has_equal_operator_impl<T>::value ||
is_comparable_type<T>::value>;
// 偏特化方案 1
// 如果类型可以比较,并且不是数组,返回lhs == rhs,并通过ok返回给调用者比较成功
template<typename T>
typename std::enable_if<is_equal_comparable<T>::value &&
!std::is_array<T>::value, bool>::type
compare_equal(const T& lhs, const T& rhs, bool& ok)
{
ok = true;
return (lhs == rhs);
}
// 偏特化方案 2
// 如果类型不可比较,也不是数组,调用compare_types_equal比较
// 这个函数是从type上拿比较运算符,具体的过程在type里讲
// auto cmp_f = t.get_equal_comparator()
// return cmp_f->cmp(lhs, rhs);
// 然后调用比较运算符计算
template<typename T>
typename std::enable_if<!is_equal_comparable<T>::value &&
!std::is_array<T>::value, bool>::type
compare_equal(const T& lhs, const T& rhs, bool& ok)
{
return compare_types_equal(&lhs, &rhs, type::get<T>(), ok);
}
// 偏特化方案 3
// 如果类型不可比较并且又是数组的话
// 调用compare_array_equal进行比较
// 这个函数是会把数组分离成
// template<typename ElementType, std::size_t Count>
// struct compare_array_equal_impl<ElementType[Count]>
// 拿到元素个元素个数之后进行判断,只有lhs全部等于rhs
// 才返回true
template<typename T>
typename std::enable_if<!is_equal_comparable<T>::value &&
std::is_array<T>::value, bool>::type
compare_equal(const T& lhs, const T& rhs, bool& ok)
{
return compare_array_equal(lhs, rhs, ok);
}
// 对于算术逻辑的处理,这里调用 variant_compare_equal 函数
// 判断方式也很简单,浮点数判定采用模糊判定
// 整形数值就直接进行判断
bool variant_compare_equal(const variant& lhs,
const type& lhs_type,
const variant& rhs,
const type& rhs_type,
bool& ok)
{
ok = true;
if (is_floating_point(lhs_type) || is_floating_point(rhs_type))
{
double p1 = lhs.to_double();
double p2 = rhs.to_double();
// 计算p1和p2差的绝对值,并放大100000000000倍后和lhs和rhs中小的比较
// 如果小的话就认为相等
return (std::abs(p1 - p2) * 100000000000 0. <=
std::min(std::abs(p1), std::abs(p2)));
}
else
{
return (lhs.to_int64() == rhs.to_int64());
}
}
而比较大小的部分的代码实现和比较相等的代码实现基本是一样的, 调用variant_compare_less函数 ,函数实现的区别主要在比较过程中调用的是小于(<)运算符,这里就不在展示了。
4. variant_data_policy_small
这个policy是为了操作能够直接装入 variant_data 的数据,以下这些函数都是通过Tp:xxx(xxx就是下面的函数)的方式来调用的。
template<typename T, typename Converter>
struct variant_data_policy_small :
variant_data_base_policy<T, variant_data_policy_small<T>, Converter>
{template<typename T, typename Converter>
struct variant_data_policy_small : variant_data_base_policy<T, variant_data_policy_small<T>, Converter>
{
// 对于数据足够放入variant_data的类型实行强转
static const T& get_value(const variant_data& data)
{
return reinterpret_cast<const T&>(data);
}
// 调用析构
static void destroy(T& value)
{
value.~T();
}
// clone类似与拷贝构造,因为variant_data空间足够大,
// 可以直接调用placement new在variant_data上构造新数据
static void clone(const T& value, variant_data& dest)
{
new (&dest) T(value);
}
// 这里的交换和clone的操作一样,只是原本dest原本被认为没有值
// 因此交换给value只要析构掉value即可
static void swap(T& value, variant_data& dest)
{
new (&dest) T(value);
destroy(value);
}
// 将任意类型的数据初始化给dest
template<typename U>
static void create(U&& value, variant_data& dest)
{
new (&dest) T(std::forward<U>(value));
}
};
5. variant_data_policy_array_small
这个policy也是为了操作能够直接装入 variant_data 的数据,只不过数据比较下,比如 char[4],整个数组都是可以装入variant_data的空间的,在操作上基本和variant_data_policy_small差不多,只是在create和clone时有些区别,并且没有destroy操作。create和clone过程中需要对数组中的变量一个一个赋值,因此这里还会用到copy_array的相关模板。
// 这个模板把数组的元素类型和长度拆解出来,然后一个一个赋值
// 这里的代码做过简化,和源码不一样,方便理解
template<typename ElementType, std::size_t Count>
auto copy_array_1(const ElementType (&in)[Count],
ElementType (&out)[Count]) -> ElementType (&)[Count]
{
copy_array_impl<ElementType[Count]>()(in, out);
return out;
}
static void clone(const T& value, variant_data& dest)
{
copy_array_1(value, const_cast<typename remove_const<T>::type&>(get_value(dest)));
}
6. variant_data_policy_arithmetic
这个policy的规则基本跟前面的一样,而且不需要销毁,只需要在create和clone的时候直接把目标变量强转成对应的type然后直接赋值就可以了,下面的代码展示create的过程,clone和create一样。
template<typename U>
static RTTR_INLINE void create(U&& value, variant_data& dest)
{
reinterpret_cast<T&>(dest) = value;
}
7. variant_data_policy_big
variant_data_policy_big用于处理容量大于variant_data的数据,这是variant_data只能作为指针使用了。在前面确定variant_data时的typelist里面,也是包含指针的,所以variant_data的容量时肯定可以容纳下一个指针的。下面看一下它前面讲的policy有什么不一样的处理方法把。
template<typename T, typename Converter>
struct variant_data_policy_big : variant_data_base_policy<T, variant_data_policy_big<T>, Converter>
{
// 因为variant_data是一个指针,所以通过指针来解引用取值返回
static const T& get_value(const variant_data& data)
{
return *reinterpret_cast<T* const &>(data);
}
// 因为是指针,因此在销毁的时候需要使用delete操作
static void destroy(T& value)
{
delete &value;
}
// clone通过new一个copy构造的对象,并强转自己为该类型指针的引用赋值指针
// create 和 swap 操作一样,不展示
static void clone(const T& value, variant_data& dest)
{
reinterpret_cast<T*&>(dest) = new T(value);
}
};
8. variant_data_policy_array_big
variant_data_policy_big_array用于处理容量大于variant_data的数据,这是variant_data只能作为指针使用了。在前面确定variant_data时的typelist里面,也是包含指针的,所以variant_data的容量时肯定可以容纳下一个指针的。下面看一下它前面讲的policy有什么不一样的处理方法把。
// 取数据的时候有两个强转,后面一个是将无类型的data转成数组的指针
// 前面一个强转是从数组的指针中解引用并返回数组的引用
// 这样函数就能返回 T[] 的引用
static RTTR_INLINE const T& get_value(const variant_data& data)
{
return reinterpret_cast<const T&>(*reinterpret_cast<const T*&>(data));
}
// 这里给variant_data赋值大容量数组new出来的指针,后面调用copy_array将数组元素一个个复制
template<typename U>
static RTTR_INLINE void create(U&& value, variant_data& dest)
{
reinterpret_cast<array_dest_type&>(dest) = new T;
copy_array(value, const_cast<typename remove_const<T>::type&>(get_value(dest)));
}
9. variant_data_policy_string
variant_data_policy_string基本继承了policy big的全部功能,只是在构造的时候字符串有char*类型和std::string类型,这两种类型在rttr里面被当成同一种类型来处理,因此在构造过程也需要同时支持2中方式。而在clone过程中,因为variant_data保存的数据是std::string的指针,不会存在有char**的形式,因此clone也就可以直接套用policy big的clone方式了。
struct RTTR_API variant_data_policy_string : variant_data_policy_big<std::string, default_type_converter<std::string>>
{
template<typename U>
// 这里的构造方式直接使用new std::string的方式,因为目标类型确定,
// 构造的值必须是std::string的构造函数支持类型,然后把指针赋值给variant_data
static RTTR_INLINE void create(U&& value, variant_data& dest)
{
reinterpret_cast<std::string*&>(dest) = new std::string(std::forward<U>(value));
}
// 这里传入的是一个char*类型,因为已知传入的类型,所以把数组的长度分离出来
// 得到长度以后通过char数组的指针和长度就可以构造出一个std::string了
template<std::size_t N>
static RTTR_INLINE void create(const char (&value)[N], variant_data& dest)
{
reinterpret_cast<std::string*&>(dest) = new std::string(value, N - 1);
}
};
copy_array的完整处理
之前在small array的create, clone, swap里展示的代码是简化的,主要用作说明用,这里展示完整的处理方法。简化的处理方法只能处理一维数组,而要处理多维数组的话就需要用到下面的方法。当数组通过copy_array传入copy_array_impl时,如果数组时多维的话,会在偏特化方案2里面循环递归,直至最终拿到数组元素,终结于特化方案1。
// 偏特化方案1
template<typename ElementType>
struct copy_array_impl
{
void operator()(const ElementType &in, ElementType &out)
{
out = in;
}
};
// 偏特化方案2
template<typename ElementType, std::size_t Count>
struct copy_array_impl<ElementType[Count]>
{
void operator()(const ElementType (&in)[Count], ElementType (&out)[Count])
{
for(std::size_t i = 0; i < Count; ++i)
// 如果是数组,仍然符合偏特化方案2
copy_array_impl<ElementType>()(in[i], out[i]);
}
};
template<typename ElementType, std::size_t Count>
auto copy_array(const ElementType (&in)[Count], ElementType (&out)[Count])
-> ElementType (&)[Count]
{
copy_array_impl<ElementType[Count]>()(in, out);
return out;
}
好文章,顶!