前幾天在水母上看到的題:
正常的比較 assert(-1 < 1U) 是會(huì)失敗的。因?yàn)?-1 會(huì)提升成無(wú)符號(hào)數(shù)。
寫一個(gè)安全的比較函數(shù),使得
template <typename T1, typename T2>
int SafeIntCompare(T1 i1, T2 i2);
如果 i1 真實(shí)值 < i2,返回 -1
i1 真實(shí)值 == i2,返回 0
i1 真實(shí)值 > i2,返回 1
只有當(dāng)兩個(gè)類型一個(gè)是有符號(hào)、另一個(gè)是無(wú)符號(hào)時(shí),才需要特殊處理。
對(duì)類型的符號(hào)判斷,可以直接判斷該類型的-1是否比0小,也可以用標(biāo)準(zhǔn)庫(kù)std::numeric_limits<T>中的is_signed成員。
簡(jiǎn)單的做法:
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;
}
但由于進(jìn)行比較的兩個(gè)數(shù)可能分別是:有符號(hào)數(shù)和無(wú)符號(hào)數(shù),編譯時(shí)編譯器會(huì)給出大量的警告。
要避免有符號(hào)數(shù)和無(wú)符號(hào)數(shù)的進(jìn)行直接比較,就必須將它們都轉(zhuǎn)為同一個(gè)類型T。這個(gè)類型的確定可以采用兩種方法:
1 比較原來(lái)兩個(gè)類型是否是有符號(hào)數(shù)以及它們所占用的字節(jié)數(shù),來(lái)推斷出應(yīng)該將它們都轉(zhuǎn)為哪種類型T,這是vc那個(gè)safeint的做法。
2 采用這個(gè)trick:將這兩個(gè)類型的數(shù)(數(shù)可以取0)直接相加,得到的結(jié)果的類型就是所求的。這是因?yàn)椋簝蓚€(gè)數(shù)進(jìn)行比較時(shí),采用的類型轉(zhuǎn)換規(guī)則和兩個(gè)數(shù)相加時(shí)所采用的規(guī)則是一致的。

改成后的代碼
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);
}
但上面的寫法有一個(gè)問(wèn)題:如果一個(gè) short和一個(gè)unsigned char進(jìn)行比較,編譯器都是轉(zhuǎn)為int進(jìn)行比較,沒有必要進(jìn)行特殊處理(上面的代碼處理后會(huì)多一個(gè)與0的比較)。實(shí)際上,如果兩個(gè)類型都是轉(zhuǎn)為有符號(hào)類型,可以直接進(jìn)行比較。
最終代碼:
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));
}