/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace apache { namespace thrift { namespace op { namespace detail { // named comparison functions, similar to c++20 // // TODO(afuller): Dedupe/Merge with folly. inline constexpr bool is_eq(folly::ordering cmp) noexcept { return cmp == folly::ordering::eq; } inline bool is_eq(folly::partial_ordering cmp) noexcept { return cmp == folly::partial_ordering::equivalent; } inline constexpr bool is_neq(folly::ordering cmp) noexcept { return cmp != folly::ordering::eq; } inline bool is_neq(folly::partial_ordering cmp) noexcept { return cmp != folly::partial_ordering::equivalent; } inline constexpr bool is_lt(folly::ordering cmp) noexcept { return cmp == folly::ordering::lt; } inline constexpr bool is_lteq(folly::ordering cmp) noexcept { return cmp != folly::ordering::gt; } inline constexpr bool is_gt(folly::ordering cmp) noexcept { return cmp == folly::ordering::gt; } inline constexpr bool is_gteq(folly::ordering cmp) noexcept { return cmp != folly::ordering::lt; } // The 'equal to' operator. // // Delegates to operator==, by default. template struct EqualTo { static_assert(type::is_concrete_v, ""); static_assert(type::is_concrete_v, ""); bool operator()( const type::native_type& lhs, const type::native_type& rhs) const { return lhs == rhs; } }; template <> struct EqualTo { template constexpr bool operator()(const L& lhs, const R& rhs) const { return EqualTo, type::infer_tag>{}(lhs, rhs); } }; template struct EqualTo { template constexpr bool operator()(const L& lhs, const R& rhs) const { return EqualTo>{}(lhs, rhs); } }; template struct EqualTo { template constexpr bool operator()(const L& lhs, const R& rhs) const { return EqualTo, RTag>{}(lhs, rhs); } }; // The 'identical to' operator. // // Unlike other binary operators, only accepts a single tag. template struct IdenticalTo : EqualTo {}; // Delegates to EqualTo, by default. template <> struct IdenticalTo { template constexpr bool operator()(const T& lhs, const T& rhs) const { return IdenticalTo>{}(lhs, rhs); } }; // The 'less than' operator. // // For use with ordered containers. template struct LessThan : std::less<> { // Deletegates to std::less<>, by default. static_assert(type::is_concrete_v, ""); static_assert(type::is_concrete_v, ""); }; template <> struct LessThan { template constexpr bool operator()(const L& lhs, const R& rhs) const { return LessThan, type::infer_tag>{}(lhs, rhs); } }; template struct LessThan { template constexpr bool operator()(const L& lhs, const R& rhs) const { return LessThan>{}(lhs, rhs); } }; template struct LessThan { template constexpr bool operator()(const L& lhs, const R& rhs) const { return LessThan, RTag>{}(lhs, rhs); } }; // The type returned by a call to `LessThan::operator()`, if well defined. template using less_than_t = decltype(LessThan{}( std::declval&>(), std::declval&>())); // If the give tags are comparable using LessThan. template inline constexpr bool less_than_comparable_v = folly::is_detected_v; // Resolves to R, if the two tags can be used together in LessThan. template using if_less_than_comparable = folly::type_t>; // A CompareWith implementation that delegates to EqualTo and LessThan. template < typename LTag, typename RTag = LTag, typename EqualTo = EqualTo, typename LessThan = LessThan, typename L = type::native_type, typename R = type::native_type, typename = if_less_than_comparable> struct DefaultCompareWith { constexpr folly::ordering operator()(const L& lhs, const R& rhs) const { if (equalTo(lhs, rhs)) { return folly::ordering::eq; } else if (lessThan(lhs, rhs)) { return folly::ordering::lt; } return folly::ordering::gt; } protected: EqualTo equalTo; LessThan lessThan; }; // The 'compare with' operator. // // TODO(afuller): Add more efficient specializations. template struct CompareWith : DefaultCompareWith {}; // Delegates by default. template <> struct CompareWith { template constexpr folly::ordering operator()(const L& lhs, const R& rhs) const { return CompareWith, type::infer_tag>{}(lhs, rhs); } }; template struct CompareWith { template constexpr folly::ordering operator()(const L& lhs, const R& rhs) const { return CompareWith>{}(lhs, rhs); } }; template struct CompareWith { template constexpr folly::ordering operator()(const L& lhs, const R& rhs) const { return CompareWith, RTag>{}(lhs, rhs); } }; // The type returned by a call to `CompareWith::operator()`, if well defined. template using compare_with_t = decltype(CompareWith{}( std::declval&>(), std::declval&>())); // If the give tags are comparable. template inline constexpr bool comparable_v = folly::is_detected_v; // Resolves to R, if the two tags *can* be used together in CompareWith. template < typename LTag, typename RTag = LTag, typename R = folly::partial_ordering> using if_comparable = folly::type_t>; // Resolves to R, if the two tags *cannot* be used together in CompareWith. template < typename LTag, typename RTag = LTag, typename R = folly::partial_ordering> using if_not_comparable = std::enable_if_t, R>; // An EqualTo that delegates to CompareWith. template < typename LTag, typename RTag = LTag, typename CompareWith = CompareWith, typename L = type::native_type, typename R = type::native_type> struct DefaultEqualTo { constexpr bool operator()(const L& lhs, const R& rhs) const { return is_eq(compareWith(lhs, rhs)); } protected: CompareWith compareWith; }; // A LessThan that delegates to CompareWith. template < typename LTag, typename RTag = LTag, typename CompareWith = CompareWith, typename L = type::native_type, typename R = type::native_type> struct DefaultLessThan { constexpr bool operator()(const L& lhs, const R& rhs) const { return is_lt(compareWith(lhs, rhs)); } protected: CompareWith compareWith; }; // Use bit_cast for floating point identical. template struct FloatIdenticalTo { bool operator()(F lhs, F rhs) const { // NOTE: Thrift specifies that all NaN variations are considered // 'identical'; however, we do not implement that here for performance // reasons. return folly::bit_cast(lhs) == folly::bit_cast(rhs); } }; template <> struct IdenticalTo : FloatIdenticalTo {}; template <> struct IdenticalTo : FloatIdenticalTo {}; // Delegate all IOBuf comparisons directly to folly. template struct CheckIOBufOp { static_assert( type::is_a_v && type::is_a_v, "expected string or binary"); }; template struct EqualTo< type::cpp_type, type::cpp_type> : CheckIOBufOp, folly::IOBufEqualTo {}; template struct EqualTo< type::cpp_type, LUTag>, type::cpp_type, RUTag>> : CheckIOBufOp, folly::IOBufEqualTo {}; template struct LessThan< type::cpp_type, type::cpp_type> : CheckIOBufOp, folly::IOBufLess {}; template struct LessThan< type::cpp_type, LUTag>, type::cpp_type, RUTag>> : CheckIOBufOp, folly::IOBufLess {}; template struct CompareWith< type::cpp_type, type::cpp_type> : CheckIOBufOp, folly::IOBufCompare {}; template struct CompareWith< type::cpp_type, LUTag>, type::cpp_type, RUTag>> : CheckIOBufOp, folly::IOBufCompare {}; template [[maybe_unused]] bool sortAndLexicographicalCompare( const T& lhs, const T& rhs, Comp&& comp) { std::vector l, r; for (auto i = lhs.begin(); i != lhs.end(); ++i) { l.push_back(i); } for (auto i = rhs.begin(); i != rhs.end(); ++i) { r.push_back(i); } auto less = [&](auto lhsIter, auto rhsIter) { return comp(*lhsIter, *rhsIter); }; std::sort(l.begin(), l.end(), less); std::sort(r.begin(), r.end(), less); return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end(), less); } template class LessThanImpl = LessThan> struct ListLessThan { bool operator()(const T& l, const T& r) const { return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end(), LessThanImpl{}); } }; template class LessThanImpl = LessThan> struct SetLessThan { bool operator()(const T& lhs, const T& rhs) const { return sortAndLexicographicalCompare(lhs, rhs, LessThanImpl{}); } }; template < class T, class K, class V, template class LessThanImpl = LessThan> struct MapLessThan { bool operator()(const T& lhs, const T& rhs) const { auto less = [](const auto& l, const auto& r) { if (LessThanImpl{}(l.first, r.first)) { return true; } if (LessThanImpl{}(r.first, l.first)) { return false; } return LessThanImpl{}(l.second, r.second); }; return sortAndLexicographicalCompare(lhs, rhs, less); } }; template struct LessThan< type::cpp_type>, type::cpp_type>> : std::conditional_t< folly::is_invocable_v, const T&, const T&>, std::less<>, ListLessThan> {}; template struct LessThan, type::list> { // Ideally this should be a non-template function. But there are cases like // // @cpp.Type={name="fbvector>"} // typedef list> doubleListList // // In which case we use `fbvector` with tag `list` type // Ideally it should be migrated to the following code // // @cpp.Type={template="fbvector"} // typedef list doubleList // @cpp.Type={template="fbvector"} // typedef list doubleListList // // TODO: Migrate all the cases above and make this function non-template. template >> bool operator()(const T& lhs, const T& rhs) const { // `std::vector::operator<` has the same implementation as this funcion. // https://github.com/gcc-mirror/gcc/blob/6cb2f2c7f36c999590a949f663d6057cbc67271f/libstdc%2B%2B-v3/include/bits/stl_vector.h#L2077-L2081 return std::lexicographical_compare( lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), LessThan{}); } }; template struct LessThan< type::cpp_type>, type::cpp_type>> : std::conditional_t< folly::is_invocable_v, const T&, const T&>, std::less<>, SetLessThan> {}; template struct LessThan< type::cpp_type>, type::cpp_type>> : std::conditional_t< folly::is_invocable_v, const T&, const T&>, std::less<>, MapLessThan> {}; // Identical for lists. template < typename VTag, typename Tag = type::list, typename T = type::native_type> struct ListIdenticalTo { bool operator()(const T& lhs, const T& rhs) const { if (lhs.size() != rhs.size()) { return false; } return std::equal(lhs.begin(), lhs.end(), rhs.begin(), cmp); } protected: IdenticalTo cmp; }; template struct IdenticalTo> : ListIdenticalTo {}; template struct IdenticalTo>> : ListIdenticalTo>> {}; template struct EqualTo> { // TODO: Similar to LessThan version, this should be a non-template function. template >> bool operator()(const T& lhs, const T& rhs) const { // `std::vector::operator==` has the same implementation as this funcion. // https://github.com/gcc-mirror/gcc/blob/6cb2f2c7f36c999590a949f663d6057cbc67271f/libstdc%2B%2B-v3/include/bits/stl_vector.h#L2037-L2042 return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin(), EqualTo{}); } }; // Identical for sets. template < typename KTag, typename Tag = type::set, typename T = type::native_type> struct SetIdenticalTo { bool operator()(const T& lhs, const T& rhs) const { if (lhs.size() != rhs.size()) { return false; } // Build a map from hash to entry. auto hashMap = createHashMap(lhs); // Check that all entries match. for (const auto& key : rhs) { if (!inRange(key, hashMap.equal_range(op::hash(key)))) { return false; } } return true; } private: // Create a multimap from hash(key)->&key static auto createHashMap(const T& set) { std::unordered_multimap hashMap; for (const auto& key : set) { hashMap.emplace(op::hash(key), &key); } return hashMap; } // Check if an identical key is in the given range. template static bool inRange(const K& key, const R& range) { for (auto itr = range.first; itr != range.second; ++itr) { if (IdenticalTo()(*itr->second, key)) { return true; } } return false; } }; template struct IdenticalTo> : SetIdenticalTo {}; template struct IdenticalTo>> : SetIdenticalTo>> {}; // Identical for maps. template < typename KTag, typename VTag, template typename Equality, typename Tag = type::map, typename T = type::native_type> struct MapEquality { bool operator()(const T& lhs, const T& rhs) const { if (lhs.size() != rhs.size()) { return false; } // Build a map from hash to entry. auto hashMap = createHashMap(lhs); // Check that all entries match. for (const auto& entry : rhs) { if (!inRange(entry, hashMap.equal_range(op::hash(entry.first)))) { return false; } } return true; } private: // Create a multimap from hash(key)->&pair(key, value) static auto createHashMap(const T& map) { std::unordered_multimap hashMap; for (const auto& entry : map) { hashMap.emplace(op::hash(entry.first), &entry); } return hashMap; } // Check if an identical pair(key, value) exists in the range. template static bool inRange(const E& entry, const R& range) { for (auto itr = range.first; itr != range.second; ++itr) { if (Equality()(itr->second->first, entry.first)) { // Found the right key! The values must match. return Equality()(itr->second->second, entry.second); } } return false; } }; template struct IdenticalTo> : MapEquality {}; template struct IdenticalTo>> : MapEquality< KTag, VTag, IdenticalTo, type::cpp_type>> {}; template struct EqualTo>> : std::conditional_t< folly::is_invocable_v< std::equal_to<>, const type::native_type&, const type::native_type&> && folly::is_invocable_v< std::equal_to<>, const type::native_type&, const type::native_type&>, std::equal_to<>, MapEquality< KTag, VTag, EqualTo, type::cpp_type>>> {}; // TODO(dokwon): Support field_ref types. template struct IdenticalTo> : IdenticalTo {}; // Hooks for adapted types. template struct EqualTo> { using adapted_tag = type::adapted; static_assert(type::is_concrete_v, ""); template constexpr bool operator()(const T& lhs, const T& rhs) const { auto useAdapter = [](auto adapter) -> folly::void_t {}; if constexpr (folly::is_invocable_v) { return Adapter::equal(lhs, rhs); } else if constexpr (folly::is_invocable_v< std::equal_to<>, const T&, const T&>) { return lhs == rhs; } else { return EqualTo{}(Adapter::toThrift(lhs), Adapter::toThrift(rhs)); } } }; template struct LessThan> { using adapted_tag = type::adapted; static_assert(type::is_concrete_v, ""); template constexpr bool operator()(const T& lhs, const T& rhs) const { auto useAdapter = [](auto adapter) -> folly::void_t {}; if constexpr (folly::is_invocable_v) { return Adapter::less(lhs, rhs); } else if constexpr (folly:: is_invocable_v, const T&, const T&>) { return lhs < rhs; } else { return LessThan{}(Adapter::toThrift(lhs), Adapter::toThrift(rhs)); } } }; template