前几天在水母上看到的题:
正常的比较 assert(-1 < 1U) 是会失败的。因为 -1 会提升成无符号数。
写一个安全的比较函数,使得
template <typename T1, typename T2>
int SafeIntCompare(T1 i1, T2 i2);
如果 i1 真实值 < i2,返回 -1
i1 真实值 == i2,返回 0
i1 真实值 > i2,返回 1
只有当两个类型一个是有符号、另一个是无符号时,才需要特殊处理。
对类型的符号判断,可以直接判断该类型的-1是否比0小,也可以用标准库std::numeric_limits<T>中的is_signed成员。
简单的做法:
template<typename T1, typename T2>
int SafeIntCompare(T1 v1, T2 v2)
{
static const bool t1 = std::numeric_limits<T1>::is_signed;
static const bool t2 = std::numeric_limits<T2>::is_signed;
if (t1 != t2) {
if (t1 && v1 < 0) return -1;
if (t2 && v2 < 0) return 1;
}
if (v1 == v2) return 0;
if (v1 < v2) return -1;
return 1;
}
但由于进行比较的两个数可能分别是:有符号数和无符号数,编译时编译器会给出大量的警告。
要避免有符号数和无符号数的进行直接比较,就必须将它们都转为同一个类型T。这个类型的确定可以采用两种方法:
1 比较原来两个类型是否是有符号数以及它们所占用的字节数,来推断出应该将它们都转为哪种类型T,这是vc那个safeint的做法。
2 采用这个trick:将这两个类型的数(数可以取0)直接相加,得到的结果的类型就是所求的。这是因为:两个数进行比较时,采用的类型转换规则和两个数相加时所采用的规则是一致的。
改成后的代码
template<bool> struct Assert {};
template<> struct Assert<false>;
template<bool is_first_negtive, bool is_second_negtive>
struct SafeIntCmpImpl
{
template<typename T1, typename T2>
static int int_cmp(T1 v1, T2 v2)
{
if (v1 == v2) return 0;
if (v1 < v2) return -1;
return 1;
}
};
template<>
struct SafeIntCmpImpl<true, false>
{
template<typename T1, typename T2, typename T3>
static int int_cmp(T1 v1, T2 v2, T3)
{
return SafeIntCmpImpl<true, true>::int_cmp(T3(v1), T3(v2));
}
template<typename T1, typename T2>
static int int_cmp(T1 v1, T2 v2)
{
return v1 < 0 ? -1 : int_cmp(v1, v2, T1(0) + T2(0));
}
};
template<>
struct SafeIntCmpImpl<false, true>
{
template<typename T1, typename T2>
static int int_cmp(T1 v1, T2 v2)
{
return -SafeIntCmpImpl<true, false>::int_cmp(v2, v1);
}
};
template<typename T1, typename T2>
int SafeIntCompare(T1 v1, T2 v2)
{
typedef std::numeric_limits<T1> M1;
typedef std::numeric_limits<T2> M2;
static const bool is_arg_valid = M1::is_integer & M2::is_integer;
Assert<is_arg_valid>();
return SafeIntCmpImpl<M1::is_signed, M2::is_signed>::int_cmp(v1, v2);
}
但上面的写法有一个问题:如果一个 short和一个unsigned char进行比较,编译器都是转为int进行比较,没有必要进行特殊处理(上面的代码处理后会多一个与0的比较)。实际上,如果两个类型都是转为有符号类型,可以直接进行比较。
最终代码:
template<typename T>
struct IsSigned {
static const bool value = T(-1) < T(0);
};
template<bool> struct Assert {};
template<> struct Assert<false>;
template<int> struct Type {};
typedef Type<0> TagNormal;
typedef Type<1> TagFirstArgIsSigned;
typedef Type<2> TagSecondArgIsSigned;
template<typename T1, typename T2, typename T3>
int SafeIntCompare(T1 v1, T2 v2, T3, TagNormal)
{
if (v1 < v2) return -1;
if (v1 == v2) return 0;
return 1;
}
template<typename T1, typename T2, typename T3>
int SafeIntCompare(T1 v1, T2 v2, T3 v3, TagFirstArgIsSigned)
{
if (v1 < 0) return -1;
return SafeIntCompare(T3(v1), T3(v2), v3, TagNormal());
}
template<typename T1, typename T2, typename T3>
int SafeIntCompare(T1 v1, T2 v2, T3 v3, TagSecondArgIsSigned)
{
if (v2 < 0) return 1;
return SafeIntCompare(T3(v1), T3(v2), v3, TagNormal());
}
template<typename T1, typename T2, typename T3>
int SafeIntCompare(T1 v1, T2 v2, T3 v3)
{
typedef std::numeric_limits<T1> M1;
typedef std::numeric_limits<T2> M2;
typedef std::numeric_limits<T3> M3;
static const bool is_arg_valid = M1::is_integer & M2::is_integer;
Assert<is_arg_valid>();
static const int type_idx = M3::is_signed ? 0 : (M1::is_signed + M2::is_signed * 2) % 3;
return SafeIntCompare(v1, v2, v3, Type<type_idx>());
}
template<typename T1, typename T2>
int SafeIntCompare(T1 v1, T2 v2)
{
return SafeIntCompare(v1, v2, T1(0) + T2(0));
}