前幾天在水母上看到的題:
正常的比較 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));
}