1 /* 2 * Copyright 2016 The WebRTC Project Authors. All rights reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 // This file defines six constexpr functions: 12 // 13 // rtc::SafeEq // == 14 // rtc::SafeNe // != 15 // rtc::SafeLt // < 16 // rtc::SafeLe // <= 17 // rtc::SafeGt // > 18 // rtc::SafeGe // >= 19 // 20 // They each accept two arguments of arbitrary types, and in almost all cases, 21 // they simply call the appropriate comparison operator. However, if both 22 // arguments are integers, they don't compare them using C++'s quirky rules, 23 // but instead adhere to the true mathematical definitions. It is as if the 24 // arguments were first converted to infinite-range signed integers, and then 25 // compared, although of course nothing expensive like that actually takes 26 // place. In practice, for signed/signed and unsigned/unsigned comparisons and 27 // some mixed-signed comparisons with a compile-time constant, the overhead is 28 // zero; in the remaining cases, it is just a few machine instructions (no 29 // branches). 30 31 #ifndef RTC_BASE_NUMERICS_SAFE_COMPARE_H_ 32 #define RTC_BASE_NUMERICS_SAFE_COMPARE_H_ 33 34 #include <stddef.h> 35 #include <stdint.h> 36 37 #include <type_traits> 38 #include <utility> 39 40 #include "rtc_base/type_traits.h" 41 42 namespace rtc { 43 44 namespace safe_cmp_impl { 45 46 template <size_t N> 47 struct LargerIntImpl : std::false_type {}; 48 template <> 49 struct LargerIntImpl<sizeof(int8_t)> : std::true_type { 50 using type = int16_t; 51 }; 52 template <> 53 struct LargerIntImpl<sizeof(int16_t)> : std::true_type { 54 using type = int32_t; 55 }; 56 template <> 57 struct LargerIntImpl<sizeof(int32_t)> : std::true_type { 58 using type = int64_t; 59 }; 60 61 // LargerInt<T1, T2>::value is true iff there's a signed type that's larger 62 // than T1 (and no larger than the larger of T2 and int*, for performance 63 // reasons); and if there is such a type, LargerInt<T1, T2>::type is an alias 64 // for it. 65 template <typename T1, typename T2> 66 struct LargerInt 67 : LargerIntImpl<sizeof(T1) < sizeof(T2) || sizeof(T1) < sizeof(int*) 68 ? sizeof(T1) 69 : 0> {}; 70 71 template <typename T> 72 constexpr typename std::make_unsigned<T>::type MakeUnsigned(T a) { 73 return static_cast<typename std::make_unsigned<T>::type>(a); 74 } 75 76 // Overload for when both T1 and T2 have the same signedness. 77 template <typename Op, 78 typename T1, 79 typename T2, 80 typename std::enable_if<std::is_signed<T1>::value == 81 std::is_signed<T2>::value>::type* = nullptr> 82 constexpr bool Cmp(T1 a, T2 b) { 83 return Op::Op(a, b); 84 } 85 86 // Overload for signed - unsigned comparison that can be promoted to a bigger 87 // signed type. 88 template <typename Op, 89 typename T1, 90 typename T2, 91 typename std::enable_if<std::is_signed<T1>::value && 92 std::is_unsigned<T2>::value && 93 LargerInt<T2, T1>::value>::type* = nullptr> 94 constexpr bool Cmp(T1 a, T2 b) { 95 return Op::Op(a, static_cast<typename LargerInt<T2, T1>::type>(b)); 96 } 97 98 // Overload for unsigned - signed comparison that can be promoted to a bigger 99 // signed type. 100 template <typename Op, 101 typename T1, 102 typename T2, 103 typename std::enable_if<std::is_unsigned<T1>::value && 104 std::is_signed<T2>::value && 105 LargerInt<T1, T2>::value>::type* = nullptr> 106 constexpr bool Cmp(T1 a, T2 b) { 107 return Op::Op(static_cast<typename LargerInt<T1, T2>::type>(a), b); 108 } 109 110 // Overload for signed - unsigned comparison that can't be promoted to a bigger 111 // signed type. 112 template <typename Op, 113 typename T1, 114 typename T2, 115 typename std::enable_if<std::is_signed<T1>::value && 116 std::is_unsigned<T2>::value && 117 !LargerInt<T2, T1>::value>::type* = nullptr> 118 constexpr bool Cmp(T1 a, T2 b) { 119 return a < 0 ? Op::Op(-1, 0) : Op::Op(safe_cmp_impl::MakeUnsigned(a), b); 120 } 121 122 // Overload for unsigned - signed comparison that can't be promoted to a bigger 123 // signed type. 124 template <typename Op, 125 typename T1, 126 typename T2, 127 typename std::enable_if<std::is_unsigned<T1>::value && 128 std::is_signed<T2>::value && 129 !LargerInt<T1, T2>::value>::type* = nullptr> 130 constexpr bool Cmp(T1 a, T2 b) { 131 return b < 0 ? Op::Op(0, -1) : Op::Op(a, safe_cmp_impl::MakeUnsigned(b)); 132 } 133 134 #define RTC_SAFECMP_MAKE_OP(name, op) \ 135 struct name { \ 136 template <typename T1, typename T2> \ 137 static constexpr bool Op(T1 a, T2 b) { \ 138 return a op b; \ 139 } \ 140 }; 141 RTC_SAFECMP_MAKE_OP(EqOp, ==) 142 RTC_SAFECMP_MAKE_OP(NeOp, !=) 143 RTC_SAFECMP_MAKE_OP(LtOp, <) 144 RTC_SAFECMP_MAKE_OP(LeOp, <=) 145 RTC_SAFECMP_MAKE_OP(GtOp, >) 146 RTC_SAFECMP_MAKE_OP(GeOp, >=) 147 #undef RTC_SAFECMP_MAKE_OP 148 149 } // namespace safe_cmp_impl 150 151 #define RTC_SAFECMP_MAKE_FUN(name) \ 152 template <typename T1, typename T2> \ 153 constexpr \ 154 typename std::enable_if<IsIntlike<T1>::value && IsIntlike<T2>::value, \ 155 bool>::type Safe##name(T1 a, T2 b) { \ 156 /* Unary plus here turns enums into real integral types. */ \ 157 return safe_cmp_impl::Cmp<safe_cmp_impl::name##Op>(+a, +b); \ 158 } \ 159 template <typename T1, typename T2> \ 160 constexpr \ 161 typename std::enable_if<!IsIntlike<T1>::value || !IsIntlike<T2>::value, \ 162 bool>::type Safe##name(const T1& a, \ 163 const T2& b) { \ 164 return safe_cmp_impl::name##Op::Op(a, b); \ 165 } 166 RTC_SAFECMP_MAKE_FUN(Eq) 167 RTC_SAFECMP_MAKE_FUN(Ne) 168 RTC_SAFECMP_MAKE_FUN(Lt) 169 RTC_SAFECMP_MAKE_FUN(Le) 170 RTC_SAFECMP_MAKE_FUN(Gt) 171 RTC_SAFECMP_MAKE_FUN(Ge) 172 #undef RTC_SAFECMP_MAKE_FUN 173 174 } // namespace rtc 175 176 #endif // RTC_BASE_NUMERICS_SAFE_COMPARE_H_ 177