blob: 39fb917a700862312c823bf12febb3dbc0fdf1b7 [file] [log] [blame]
kwiberg8a44e1d2016-11-01 12:04:26 -07001/*
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 functions:
12//
13// rtc::safe_cmp::Eq // ==
14// rtc::safe_cmp::Ne // !=
15// rtc::safe_cmp::Lt // <
16// rtc::safe_cmp::Le // <=
17// rtc::safe_cmp::Gt // >
18// rtc::safe_cmp::Ge // >=
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 WEBRTC_BASE_SAFE_COMPARE_H_
32#define WEBRTC_BASE_SAFE_COMPARE_H_
33
34#include <stddef.h>
35#include <stdint.h>
36
37#include <type_traits>
38#include <utility>
39
40namespace rtc {
41namespace safe_cmp {
42
43namespace safe_cmp_impl {
44
45template <size_t N>
46struct LargerIntImpl : std::false_type {};
47template <>
48struct LargerIntImpl<sizeof(int8_t)> : std::true_type {
49 using type = int16_t;
50};
51template <>
52struct LargerIntImpl<sizeof(int16_t)> : std::true_type {
53 using type = int32_t;
54};
55template <>
56struct LargerIntImpl<sizeof(int32_t)> : std::true_type {
57 using type = int64_t;
58};
59
60// LargerInt<T1, T2>::value is true iff there's a signed type that's larger
61// than T1 (and no larger than the larger of T2 and int*, for performance
62// reasons); and if there is such a type, LargerInt<T1, T2>::type is an alias
63// for it.
64template <typename T1, typename T2>
65struct LargerInt
66 : LargerIntImpl<sizeof(T1) < sizeof(T2) || sizeof(T1) < sizeof(int*)
67 ? sizeof(T1)
68 : 0> {};
69
70template <typename T>
71inline typename std::make_unsigned<T>::type MakeUnsigned(T a) {
72 return static_cast<typename std::make_unsigned<T>::type>(a);
73}
74
75// Overload for when both T1 and T2 have the same signedness.
76template <typename Op,
77 typename T1,
78 typename T2,
79 typename std::enable_if<std::is_signed<T1>::value ==
80 std::is_signed<T2>::value>::type* = nullptr>
81inline bool Cmp(T1 a, T2 b) {
82 return Op::Op(a, b);
83}
84
85// Overload for signed - unsigned comparison that can be promoted to a bigger
86// signed type.
87template <typename Op,
88 typename T1,
89 typename T2,
90 typename std::enable_if<std::is_signed<T1>::value &&
91 std::is_unsigned<T2>::value &&
92 LargerInt<T2, T1>::value>::type* = nullptr>
93inline bool Cmp(T1 a, T2 b) {
94 return Op::Op(a, static_cast<typename LargerInt<T2, T1>::type>(b));
95}
96
97// Overload for unsigned - signed comparison that can be promoted to a bigger
98// signed type.
99template <typename Op,
100 typename T1,
101 typename T2,
102 typename std::enable_if<std::is_unsigned<T1>::value &&
103 std::is_signed<T2>::value &&
104 LargerInt<T1, T2>::value>::type* = nullptr>
105inline bool Cmp(T1 a, T2 b) {
106 return Op::Op(static_cast<typename LargerInt<T1, T2>::type>(a), b);
107}
108
109// Overload for signed - unsigned comparison that can't be promoted to a bigger
110// signed type.
111template <typename Op,
112 typename T1,
113 typename T2,
114 typename std::enable_if<std::is_signed<T1>::value &&
115 std::is_unsigned<T2>::value &&
116 !LargerInt<T2, T1>::value>::type* = nullptr>
117inline bool Cmp(T1 a, T2 b) {
118 return a < 0 ? Op::Op(-1, 0) : Op::Op(safe_cmp_impl::MakeUnsigned(a), b);
119}
120
121// Overload for unsigned - signed comparison that can't be promoted to a bigger
122// signed type.
123template <typename Op,
124 typename T1,
125 typename T2,
126 typename std::enable_if<std::is_unsigned<T1>::value &&
127 std::is_signed<T2>::value &&
128 !LargerInt<T1, T2>::value>::type* = nullptr>
129inline bool Cmp(T1 a, T2 b) {
130 return b < 0 ? Op::Op(0, -1) : Op::Op(a, safe_cmp_impl::MakeUnsigned(b));
131}
132
133#define RTC_SAFECMP_MAKE_OP(name, op) \
134 struct name { \
135 template <typename T1, typename T2> \
136 static constexpr bool Op(T1 a, T2 b) { \
137 return a op b; \
138 } \
139 };
140RTC_SAFECMP_MAKE_OP(EqOp, ==)
141RTC_SAFECMP_MAKE_OP(NeOp, !=)
142RTC_SAFECMP_MAKE_OP(LtOp, <)
143RTC_SAFECMP_MAKE_OP(LeOp, <=)
144RTC_SAFECMP_MAKE_OP(GtOp, >)
145RTC_SAFECMP_MAKE_OP(GeOp, >=)
146#undef RTC_SAFECMP_MAKE_OP
147
kwiberg352444f2016-11-28 15:58:53 -0800148// Determines if the given type is an enum that converts implicitly to
149// an integral type.
150template <typename T>
151struct IsIntEnum {
152 private:
153 // This overload is used if the type is an enum, and unary plus
154 // compiles and turns it into an integral type.
155 template <typename X,
156 typename std::enable_if<
157 std::is_enum<X>::value &&
158 std::is_integral<decltype(+std::declval<X>())>::value>::type* =
159 nullptr>
160 static int Test(int);
161
162 // Otherwise, this overload is used.
163 template <typename>
164 static char Test(...);
165
166 public:
167 static constexpr bool value =
168 std::is_same<decltype(Test<typename std::remove_reference<T>::type>(0)),
169 int>::value;
170};
171
172// Determines if the given type is integral, or an enum that
173// converts implicitly to an integral type.
174template <typename T>
175struct IsIntlike {
176 private:
177 using X = typename std::remove_reference<T>::type;
178
179 public:
180 static constexpr bool value =
181 std::is_integral<X>::value || IsIntEnum<X>::value;
182};
183
184namespace test_enum_intlike {
185
186enum E1 { e1 };
187enum { e2 };
188enum class E3 { e3 };
189struct S {};
190
191static_assert(IsIntEnum<E1>::value, "");
192static_assert(IsIntEnum<decltype(e2)>::value, "");
193static_assert(!IsIntEnum<E3>::value, "");
194static_assert(!IsIntEnum<int>::value, "");
195static_assert(!IsIntEnum<float>::value, "");
196static_assert(!IsIntEnum<S>::value, "");
197
198static_assert(IsIntlike<E1>::value, "");
199static_assert(IsIntlike<decltype(e2)>::value, "");
200static_assert(!IsIntlike<E3>::value, "");
201static_assert(IsIntlike<int>::value, "");
202static_assert(!IsIntlike<float>::value, "");
203static_assert(!IsIntlike<S>::value, "");
204
205} // test_enum_intlike
kwiberg8a44e1d2016-11-01 12:04:26 -0700206} // namespace safe_cmp_impl
207
kwiberg352444f2016-11-28 15:58:53 -0800208#define RTC_SAFECMP_MAKE_FUN(name) \
209 template <typename T1, typename T2, \
210 typename std::enable_if< \
211 safe_cmp_impl::IsIntlike<T1>::value && \
212 safe_cmp_impl::IsIntlike<T2>::value>::type* = nullptr> \
213 inline bool name(T1 a, T2 b) { \
214 /* Unary plus here turns enums into real integral types. */ \
215 return safe_cmp_impl::Cmp<safe_cmp_impl::name##Op>(+a, +b); \
216 } \
217 template <typename T1, typename T2, \
218 typename std::enable_if< \
219 !safe_cmp_impl::IsIntlike<T1>::value || \
220 !safe_cmp_impl::IsIntlike<T2>::value>::type* = nullptr> \
221 inline bool name(T1&& a, T2&& b) { \
222 return safe_cmp_impl::name##Op::Op(a, b); \
kwiberg8a44e1d2016-11-01 12:04:26 -0700223 }
224RTC_SAFECMP_MAKE_FUN(Eq)
225RTC_SAFECMP_MAKE_FUN(Ne)
226RTC_SAFECMP_MAKE_FUN(Lt)
227RTC_SAFECMP_MAKE_FUN(Le)
228RTC_SAFECMP_MAKE_FUN(Gt)
229RTC_SAFECMP_MAKE_FUN(Ge)
230#undef RTC_SAFECMP_MAKE_FUN
231
232} // namespace safe_cmp
233} // namespace rtc
234
235#endif // WEBRTC_BASE_SAFE_COMPARE_H_