ComparisonOperators.h (9711B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* 8 * Support comparison operations on wrapper types -- e.g. |JS::Rooted<T>|, 9 * |JS::Handle<T>|, and so on -- against raw |T| values, against pointers or 10 * |nullptr| if the wrapper is a pointer wrapper, and against other wrappers 11 * around compatible types. 12 */ 13 14 #ifndef js_ComparisonOperators_h 15 #define js_ComparisonOperators_h 16 17 #include <type_traits> // std::false_type, std::true_type, std::enable_if_t, std::is_pointer_v, std::remove_pointer_t 18 19 // To define |operator==| and |operator!=| for a wrapper class |W| (which may 20 // or may not be a template class) that contains a |T|: 21 // 22 // * Specialize |JS::detail::DefineComparisonOps| for |W|: 23 // - Make it inherit from |std::true_type|. 24 // - Include within your specialization a |static get(const W& v)| function 25 // that returns the value (which may be an lvalue reference) of the |T| in 26 // |W|. 27 // * If needed, add |using JS::detail::wrapper_comparison::operator==;| and 28 // |using JS::detail::wrapper_comparison::operator!=;| to the namespace 29 // directly containing |W| at the end of this header. (If you are not in 30 // SpiderMonkey code and have questionably decided to define your own 31 // wrapper class, add these to its namespace somewhere in your code.) 32 // 33 // The first step opts the wrapper class into comparison support and defines a 34 // generic means of extracting a comparable |T| out of an instance. 35 // 36 // The second step ensures that symmetric |operator==| and |operator!=| are 37 // exposed for the wrapper, accepting two wrappers or a wrapper and a suitable 38 // raw value. 39 // 40 // Failure to perform *both* steps will likely result in errors like 41 // 'invalid operands to binary expression' or 'no match for operator==' 42 // when comparing an instance of your wrapper. 43 44 namespace JS { 45 46 namespace detail { 47 48 // By default, comparison ops are not supported for types. 49 template <typename T> 50 struct DefineComparisonOps : std::false_type {}; 51 52 // Define functions for the core equality operations, that the actual operators 53 // can all invoke. 54 55 // Compare two wrapper types. Assumes both wrapper types support comparison 56 // operators. 57 template <typename W, typename OW> 58 inline bool WrapperEqualsWrapper(const W& wrapper, const OW& other) { 59 return JS::detail::DefineComparisonOps<W>::get(wrapper) == 60 JS::detail::DefineComparisonOps<OW>::get(other); 61 } 62 63 // Compare a wrapper against a value of its unwrapped element type (or against a 64 // value that implicitly converts to that unwrapped element type). Assumes its 65 // wrapper argument supports comparison operators. 66 template <typename W> 67 inline bool WrapperEqualsUnwrapped(const W& wrapper, 68 const typename W::ElementType& value) { 69 return JS::detail::DefineComparisonOps<W>::get(wrapper) == value; 70 } 71 72 // Compare a wrapper containing a pointer against a pointer to const element 73 // type. Assumes its wrapper argument supports comparison operators. 74 template <typename W> 75 inline bool WrapperEqualsPointer( 76 const W& wrapper, 77 const typename std::remove_pointer_t<typename W::ElementType>* ptr) { 78 return JS::detail::DefineComparisonOps<W>::get(wrapper) == ptr; 79 } 80 81 // It is idiomatic C++ to define operators on user-defined types in the 82 // namespace of their operands' types (not at global scope, which isn't examined 83 // if at point of operator use another operator definition shadows the global 84 // definition). But our wrappers live in *multiple* namespaces (|namespace js| 85 // and |namespace JS| in SpiderMonkey), so we can't literally do that without 86 // defining ambiguous overloads. 87 // 88 // Instead, we define the operators *once* in a namespace containing nothing 89 // else at all. Then we |using| the operators into each namespace containing 90 // a wrapper type. |using| creates *aliases*, so two |using|s of the same 91 // operator contribute only one overload to overload resolution. 92 namespace wrapper_comparison { 93 94 // Comparisons between potentially-differing wrappers. 95 template <typename W, typename OW> 96 inline typename std::enable_if_t<JS::detail::DefineComparisonOps<W>::value && 97 JS::detail::DefineComparisonOps<OW>::value, 98 bool> 99 operator==(const W& wrapper, const OW& other) { 100 return JS::detail::WrapperEqualsWrapper(wrapper, other); 101 } 102 103 template <typename W, typename OW> 104 inline typename std::enable_if_t<JS::detail::DefineComparisonOps<W>::value && 105 JS::detail::DefineComparisonOps<OW>::value, 106 bool> 107 operator!=(const W& wrapper, const OW& other) { 108 return !JS::detail::WrapperEqualsWrapper(wrapper, other); 109 } 110 111 // Comparisons between a wrapper and its unwrapped element type. 112 template <typename W> 113 inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> 114 operator==(const W& wrapper, const typename W::ElementType& value) { 115 return WrapperEqualsUnwrapped(wrapper, value); 116 } 117 118 template <typename W> 119 inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> 120 operator!=(const W& wrapper, const typename W::ElementType& value) { 121 return !WrapperEqualsUnwrapped(wrapper, value); 122 } 123 124 template <typename W> 125 inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> 126 operator==(const typename W::ElementType& value, const W& wrapper) { 127 return WrapperEqualsUnwrapped(wrapper, value); 128 } 129 130 template <typename W> 131 inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> 132 operator!=(const typename W::ElementType& value, const W& wrapper) { 133 return !WrapperEqualsUnwrapped(wrapper, value); 134 } 135 136 // For wrappers around a pointer type, comparisons between a wrapper object 137 // and a const element pointer. 138 template <typename W> 139 inline typename std::enable_if_t<DefineComparisonOps<W>::value && 140 std::is_pointer_v<typename W::ElementType>, 141 bool> 142 operator==(const W& wrapper, 143 const typename std::remove_pointer_t<typename W::ElementType>* ptr) { 144 return WrapperEqualsPointer(wrapper, ptr); 145 } 146 147 template <typename W> 148 inline typename std::enable_if_t<DefineComparisonOps<W>::value && 149 std::is_pointer_v<typename W::ElementType>, 150 bool> 151 operator!=(const W& wrapper, 152 const typename std::remove_pointer_t<typename W::ElementType>* ptr) { 153 return !WrapperEqualsPointer(wrapper, ptr); 154 } 155 156 template <typename W> 157 inline typename std::enable_if_t<DefineComparisonOps<W>::value && 158 std::is_pointer_v<typename W::ElementType>, 159 bool> 160 operator==(const typename std::remove_pointer_t<typename W::ElementType>* ptr, 161 const W& wrapper) { 162 return WrapperEqualsPointer(wrapper, ptr); 163 } 164 165 template <typename W> 166 inline typename std::enable_if_t<DefineComparisonOps<W>::value && 167 std::is_pointer_v<typename W::ElementType>, 168 bool> 169 operator!=(const typename std::remove_pointer_t<typename W::ElementType>* ptr, 170 const W& wrapper) { 171 return !WrapperEqualsPointer(wrapper, ptr); 172 } 173 174 // For wrappers around a pointer type, comparisons between a wrapper object 175 // and |nullptr|. 176 // 177 // These overloads are a workaround for gcc hazard build bugs. Per spec, 178 // |nullptr -> const T*| for the wrapper-pointer operators immediately above 179 // this is a standard conversion sequence (consisting of a single pointer 180 // conversion). Meanwhile, |nullptr -> T* const&| for the wrapper-element 181 // operators just above that, is a pointer conversion to |T*|, then an identity 182 // conversion of the |T* const| to a reference. The former conversion sequence 183 // is a proper subsequence of the latter, so it *should* be a better conversion 184 // sequence and thus should be the better overload. But gcc doesn't implement 185 // things this way, so we add overloads directly targeting |nullptr| as an exact 186 // match, preferred to either of those overloads. 187 // 188 // We should be able to remove these overloads when gcc hazard builds use modern 189 // clang. 190 template <typename W> 191 inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> 192 operator==(const W& wrapper, std::nullptr_t) { 193 return WrapperEqualsUnwrapped(wrapper, nullptr); 194 } 195 196 template <typename W> 197 inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> 198 operator!=(const W& wrapper, std::nullptr_t) { 199 return !WrapperEqualsUnwrapped(wrapper, nullptr); 200 } 201 202 template <typename W> 203 inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> 204 operator==(std::nullptr_t, const W& wrapper) { 205 return WrapperEqualsUnwrapped(wrapper, nullptr); 206 } 207 208 template <typename W> 209 inline typename std::enable_if_t<DefineComparisonOps<W>::value, bool> 210 operator!=(std::nullptr_t, const W& wrapper) { 211 return !WrapperEqualsUnwrapped(wrapper, nullptr); 212 } 213 214 } // namespace wrapper_comparison 215 216 } // namespace detail 217 218 } // namespace JS 219 220 // Expose wrapper-supporting |operator==| and |operator!=| in the namespaces of 221 // all SpiderMonkey's wrapper classes that support comparisons. 222 223 namespace JS { 224 225 using JS::detail::wrapper_comparison::operator==; 226 using JS::detail::wrapper_comparison::operator!=; 227 228 } // namespace JS 229 230 namespace js { 231 232 using JS::detail::wrapper_comparison::operator==; 233 using JS::detail::wrapper_comparison::operator!=; 234 235 } // namespace js 236 237 #endif // js_ComparisonOperators_h