On 10/01/20 18:54 +0100, François Dumont wrote:
Hi

    Here is my attempt to improve == operator.

    There is a small optimization for the std::unordered_mutiXXX containers but the main enhancement rely on some partial template specialization of the _Equality type. I limit it to usage of unordered containers with std::equal_to to be sure that the container _Equal functor is like the key type ==.

I think we can assume that for any _Equal, not just std::equal_to:
http://eel.is/c++draft/unord.req#12.sentence-5

However ...

    Do I need to also consider user partial template specialization of std::equal_to ? It is a well know bad practice so I hope the Standard says that such a partial specialization leads to undefined behavior.

It's certainly not undefined to specialize equal_to, and I'm not sure
how to make your optimisations valid in that case.

Consider:

struct X
{
  int i;
  int rounded() const { return i - (i % 10); }
  bool operator==(X x) const { return i == x.i; }
};

template<> struct std::equal_to<X>
{
  bool operator()(X l, X r) const
  { return l.rounded() == r.rounded(); }
};

template<> std::hash<X>
{
  bool operator()(X x) const { return hash<int>()(x.rounded()); }
};

std::unordered_multiset<X> u{ X{10}, X{11}, X{12} };
std::unordered_multiset<X> v{ X{15}, X{16}, X{17} };
bool b1 = u == v;
bool b2 = std::is_permutation(u.begin(), u.end(), v.begin());
assert(b1 == b2);

I think the last new specialization in your patch would be used for
this case, and because __x_count == v.count(*u.begin()) it will say
they're equal. But the is_permutation call says they're not.

So I think the assertion would fail, but the standard says it should
pass. Am I mistaken?


    I saw that the _S_is_permutation has been done in 2012, before std::is_permutation has been added in 2013. I'll try to replace it in a future patch.

Yes, that seems like a good idea.

    PR libstdc++/91223
    * include/bits/hashtable_policy.h
    (_Equality<_Key, std::pair<const _Key, _Tp>, _Alloc,
    __detail::_Select1st, std::equal_to<_Key>, _H1, _H2, _Hash,
    _RehashPolicy, _Traits, true>): New partial template spetialization.
    (_Equality<_Value, _Value, _Alloc, __detail::_Identity,
    std::equal_to<_Value>, _H1, _H2, _Hash, _RehashPolicy, _Traits, true>):
    Likewise.
    (_Equality<_Key, std::pair<const _Key, _Tp>, _Alloc,
    __detail::_Select1st, std::equal_to<_Key>, _H1, _H2, _Hash,
    _RehashPolicy, _Traits, false>): Likewise.
    (_Equality<_Key, std::pair<const _Key, _Tp>, _Alloc,
    __detail::_Select1st, std::equal_to<_Key>, _H1, _H2, _Hash)
    (_RehashPolicy, _Traits, false>): Likewise.
    * src/c++11/hashtable_c++0x.cc: Include <bits/stl_function.h>.
    * testsuite/23_containers/unordered_multiset/operators/1.cc
    (Hash, Equal, test02, test03): New.
    * testsuite/23_containers/unordered_set/operators/1.cc
    (Hash, Equal, test02, test03): New.

unordered tests run under Linux x86_64.

Ok to commit after running all tests ?

François


diff --git a/libstdc++-v3/include/bits/hashtable_policy.h 
b/libstdc++-v3/include/bits/hashtable_policy.h
index 7bbfdfd375b..2ac3e959320 100644
--- a/libstdc++-v3/include/bits/hashtable_policy.h
+++ b/libstdc++-v3/include/bits/hashtable_policy.h
@@ -1959,11 +1959,18 @@ namespace __detail

      for (auto __itx = __this->begin(); __itx != __this->end();)
        {
-         const auto __xrange = __this->equal_range(_ExtractKey()(*__itx));
+         std::size_t __x_count = 1;
+         auto __itx_end = __itx;
+         for (++__itx_end; __itx_end != __this->end()
+                && __this->key_eq()(_ExtractKey()(*__itx_end),
+                                    _ExtractKey()(*__itx));
+              ++__itx_end)
+           ++__x_count;

This is a nice optimisation.

+         const auto __xrange = std::make_pair(__itx, __itx_end);
          const auto __yrange = __other.equal_range(_ExtractKey()(*__itx));

-         if (std::distance(__xrange.first, __xrange.second)
-             != std::distance(__yrange.first, __yrange.second))
+         if (__x_count != std::distance(__yrange.first, __yrange.second))
            return false;

          if (!_S_is_permutation(__xrange.first, __xrange.second,
@@ -1975,6 +1982,242 @@ namespace __detail
      return true;
    }

+  /// Specialization.

With the increased number of specializations I think this comment
would be more useful if it said "specialization for multimap", and
change the others to "specialization for multiset" etc.

+  template<typename _Key, typename _Tp, typename _Alloc,
+          typename _H1, typename _H2, typename _Hash,
+          typename _RehashPolicy, typename _Traits>
+    struct _Equality<_Key, std::pair<const _Key, _Tp>, _Alloc,
+                    __detail::_Select1st, std::equal_to<_Key>,
+                    _H1, _H2, _Hash, _RehashPolicy, _Traits, true>
+    {
+      using __hashtable = _Hashtable<_Key, std::pair<const _Key, _Tp>, _Alloc,
+                                    __detail::_Select1st, std::equal_to<_Key>,
+                                    _H1, _H2, _Hash, _RehashPolicy, _Traits>;
+
+      bool
+      _M_equal(const __hashtable&) const;
+    };
+
+  template<typename _Key, typename _Tp, typename _Alloc,
+          typename _H1, typename _H2, typename _Hash,
+          typename _RehashPolicy, typename _Traits>
+    bool
+    _Equality<_Key, std::pair<const _Key, _Tp>, _Alloc, __detail::_Select1st,
+             std::equal_to<_Key>, _H1, _H2,
+             _Hash, _RehashPolicy, _Traits, true>::
+    _M_equal(const __hashtable& __other) const
+    {
+      const __hashtable* __this = static_cast<const __hashtable*>(this);
+
+      if (__this->size() != __other.size())
+       return false;
+
+      for (auto __itx = __this->begin(); __itx != __this->end(); ++__itx)
+       {
+         const auto __ity = __other.find(__itx->first);
+         if (__ity == __other.end() || !bool(__ity->second == __itx->second))
+           return false;
+       }
+      return true;
+    }
+
+  /// Specialization.
+  template<typename _Value, typename _Alloc,
+          typename _H1, typename _H2, typename _Hash,
+          typename _RehashPolicy, typename _Traits>
+  struct _Equality<_Value, _Value, _Alloc, __detail::_Identity,

Wrong indentation here.

+                  std::equal_to<_Value>, _H1, _H2,
+                  _Hash, _RehashPolicy, _Traits, true>
+    {
+      using __hashtable = _Hashtable<_Value, _Value, _Alloc,
+                                    __detail::_Identity, std::equal_to<_Value>,
+                                    _H1, _H2, _Hash, _RehashPolicy, _Traits>;
+
+      bool
+      _M_equal(const __hashtable&) const;
+    };
+
+  template<typename _Value, typename _Alloc,
+          typename _H1, typename _H2, typename _Hash,
+          typename _RehashPolicy, typename _Traits>
+    bool
+    _Equality<_Value, _Value, _Alloc, __detail::_Identity,
+             std::equal_to<_Value>, _H1, _H2,
+             _Hash, _RehashPolicy, _Traits, true>::
+    _M_equal(const __hashtable& __other) const
+    {
+      const __hashtable* __this = static_cast<const __hashtable*>(this);
+
+      if (__this->size() != __other.size())
+       return false;
+
+      for (auto __itx = __this->begin(); __itx != __this->end(); ++__itx)
+       if (__other.find(*__itx) == __other.end())
+         return false;
+
+      return true;
+    }
+
+  /// Specialization.
+  template<typename _Key, typename _Tp, typename _Alloc,
+          typename _H1, typename _H2, typename _Hash,
+          typename _RehashPolicy, typename _Traits>
+    struct _Equality<_Key, std::pair<const _Key, _Tp>, _Alloc,
+                    __detail::_Select1st, std::equal_to<_Key>,
+                    _H1, _H2, _Hash, _RehashPolicy, _Traits, false>
+    {
+      using __hashtable = _Hashtable<_Key, std::pair<const _Key, _Tp>, _Alloc,
+                                    __detail::_Select1st, std::equal_to<_Key>,
+                                    _H1, _H2, _Hash, _RehashPolicy, _Traits>;
+
+      bool
+      _M_equal(const __hashtable&) const;
+
+    private:
+      using __hash_cached = typename _Traits::__hash_cached;
+      using __constant_iterators = typename _Traits::__constant_iterators;
+      using const_iterator =
+       __detail::_Node_const_iterator<std::pair<const _Key, _Tp>,
+                                      __constant_iterators::value,
+                                      __hash_cached::value>;
+
+      static bool
+      _S_is_permutation(const_iterator, const_iterator, const_iterator);
+    };
+
+  template<typename _Key, typename _Tp, typename _Alloc,
+          typename _H1, typename _H2, typename _Hash,
+          typename _RehashPolicy, typename _Traits>
+    bool
+    _Equality<_Key, std::pair<const _Key, _Tp>, _Alloc,
+             __detail::_Select1st, std::equal_to<_Key>,
+             _H1, _H2, _Hash, _RehashPolicy, _Traits, false>::
+    _S_is_permutation(const_iterator __first1, const_iterator __last1,
+                     const_iterator __first2)
+    {
+      for (; __first1 != __last1; ++__first1, ++__first2)
+       if (!(__first1->second == __first2->second))
+         break;
+
+      if (__first1 == __last1)
+       return true;
+
+      const_iterator __last2 = __first2;
+      std::advance(__last2, std::distance(__first1, __last1));
+
+      for (const_iterator __it1 = __first1; __it1 != __last1; ++__it1)
+       {
+         const_iterator __tmp =  __first1;
+         while (__tmp != __it1 && !bool(__tmp->second == __it1->second))
+           ++__tmp;
+
+         // We've seen this one before.
+         if (__tmp != __it1)
+           continue;
+
+         std::ptrdiff_t __n2 = 0;
+         for (__tmp = __first2; __tmp != __last2; ++__tmp)
+           if (__tmp->second == __it1->second)
+             ++__n2;
+
+         if (!__n2)
+           return false;
+
+         std::ptrdiff_t __n1 = 0;
+         for (__tmp = __it1; __tmp != __last1; ++__tmp)
+           if (__tmp->second == __it1->second)
+             ++__n1;
+
+         if (__n1 != __n2)
+           return false;
+       }
+      return true;
+    }
+
+  template<typename _Key, typename _Tp, typename _Alloc,
+          typename _H1, typename _H2, typename _Hash,
+          typename _RehashPolicy, typename _Traits>
+    bool
+    _Equality<_Key, std::pair<const _Key, _Tp>, _Alloc,
+             __detail::_Select1st, std::equal_to<_Key>,
+             _H1, _H2, _Hash, _RehashPolicy, _Traits, false>::
+    _M_equal(const __hashtable& __other) const
+    {
+      const __hashtable* __this = static_cast<const __hashtable*>(this);
+
+      if (__this->size() != __other.size())
+       return false;
+
+      for (auto __itx = __this->begin(); __itx != __this->end();)
+       {
+         std::size_t __x_count = 1;
+         auto __itx_end = __itx;
+         for (++__itx_end; __itx_end != __this->end()
+                && __this->key_eq()(__itx_end->first, __itx->first);
+              ++__itx_end)
+           ++__x_count;
+
+         const auto __xrange = make_pair(__itx, __itx_end);
+         const auto __yrange = __other.equal_range(__itx->first);
+
+         if (__x_count != std::distance(__yrange.first, __yrange.second))
+           return false;
+
+         if (!_S_is_permutation(__xrange.first, __xrange.second,
+                                __yrange.first))
+           return false;
+
+         __itx = __xrange.second;

Why do you use __xrange.second here, rather than __itx_end?

To me it seems clearer to use __itx_end, consistent with the
multiset case below.


+       }
+      return true;
+    }
+
+  /// Specialization.
+  template<typename _Value, typename _Alloc,
+          typename _H1, typename _H2, typename _Hash,
+          typename _RehashPolicy, typename _Traits>
+    struct _Equality<_Value, _Value, _Alloc, __detail::_Identity,
+                    std::equal_to<_Value>, _H1, _H2,
+                    _Hash, _RehashPolicy, _Traits, false>
+    {
+      using __hashtable = _Hashtable<_Value, _Value, _Alloc,
+                                    __detail::_Identity, std::equal_to<_Value>,
+                                    _H1, _H2, _Hash, _RehashPolicy, _Traits>;
+
+      bool
+      _M_equal(const __hashtable&) const;
+    };
+
+  template<typename _Value, typename _Alloc,
+          typename _H1, typename _H2, typename _Hash,
+          typename _RehashPolicy, typename _Traits>
+    bool
+    _Equality<_Value, _Value, _Alloc, __detail::_Identity,
+             std::equal_to<_Value>,
+             _H1, _H2, _Hash, _RehashPolicy, _Traits, false>::
+    _M_equal(const __hashtable& __other) const
+    {
+      const __hashtable* __this = static_cast<const __hashtable*>(this);
+
+      if (__this->size() != __other.size())
+       return false;
+
+      for (auto __itx = __this->begin(); __itx != __this->end();)
+       {
+         std::size_t __x_count = 1;
+         auto __itx_end = __itx;
+         for (++__itx_end; __itx_end != __this->end()
+                && __this->key_eq()(*__itx_end, *__itx); ++__itx_end)
+           ++__x_count;
+
+         if (__x_count != __other.count(*__itx))
+           return false;
+
+         __itx = __itx_end;
+       }
+      return true;
+    }
+
  /**
   * This type deals with all allocation and keeps an allocator instance
   * through inheritance to benefit from EBO when possible.
diff --git a/libstdc++-v3/src/c++11/hashtable_c++0x.cc 
b/libstdc++-v3/src/c++11/hashtable_c++0x.cc
index de8e2c7cb91..2054791e13a 100644
--- a/libstdc++-v3/src/c++11/hashtable_c++0x.cc
+++ b/libstdc++-v3/src/c++11/hashtable_c++0x.cc
@@ -30,6 +30,7 @@
#include <tuple>
#include <ext/aligned_buffer.h>
#include <ext/alloc_traits.h>
+#include <bits/stl_function.h> // equal_to, _Identity, _Select1st
#include <bits/hashtable_policy.h>

namespace std _GLIBCXX_VISIBILITY(default)
diff --git 
a/libstdc++-v3/testsuite/23_containers/unordered_multiset/operators/1.cc 
b/libstdc++-v3/testsuite/23_containers/unordered_multiset/operators/1.cc
index 4b87f62b74a..7252cad29c2 100644
--- a/libstdc++-v3/testsuite/23_containers/unordered_multiset/operators/1.cc
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/operators/1.cc
@@ -99,8 +99,64 @@ void test01()
  VERIFY( !(ums1 != cums2) );
}

+void test02()
+{
+  std::unordered_multiset<int> us1
+  { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9 };
+  std::unordered_multiset<int> us2
+  { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+
+  VERIFY( us1 == us2 );
+}
+
+struct Hash
+{
+  std::size_t
+  operator()(const std::pair<int, int>& p) const
+  { return p.first; }
+};
+
+struct Equal
+{
+  bool
+  operator()(const std::pair<int, int>& lhs, const std::pair<int, int>& rhs) 
const
+  { return lhs.first == rhs.first; }
+};
+
+void test03()
+{
+  std::unordered_multiset<std::pair<int, int>, Hash, Equal> us1
+  {
+    { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, { 4, 0 },
+    { 0, 1 }, { 1, 1 }, { 2, 1 }, { 3, 1 }, { 4, 1 },
+    { 5, 0 }, { 6, 0 }, { 7, 0 }, { 8, 0 }, { 9, 0 },
+    { 5, 1 }, { 6, 1 }, { 7, 1 }, { 8, 1 }, { 9, 1 }
+  };
+  std::unordered_multiset<std::pair<int, int>, Hash, Equal> us2
+  {
+    { 5, 1 }, { 6, 1 }, { 7, 1 }, { 8, 1 }, { 9, 1 },
+    { 0, 1 }, { 1, 1 }, { 2, 1 }, { 3, 1 }, { 4, 1 },
+    { 5, 0 }, { 6, 0 }, { 7, 0 }, { 8, 0 }, { 9, 0 },
+    { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, { 4, 0 }
+  };
+
+  VERIFY( us1 == us2 );
+
+  std::unordered_multiset<std::pair<int, int>, Hash, Equal> us3
+  {
+    { 5, 1 }, { 6, 1 }, { 7, 1 }, { 8, 1 }, { 9, 1 },
+    { 0, 1 }, { 1, 1 }, { 2, 1 }, { 3, 1 }, { 4, 1 },
+    { 5, 0 }, { 6, 0 }, { 7, 1 }, { 8, 0 }, { 9, 0 },
+    { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, { 4, 0 }
+  };
+
+  VERIFY( us1 != us3 );
+}
+
int main()
{
  test01();
+  test02();
+  test03();
  return 0;
}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/operators/1.cc 
b/libstdc++-v3/testsuite/23_containers/unordered_set/operators/1.cc
index d841246e2c1..36a45dfa099 100644
--- a/libstdc++-v3/testsuite/23_containers/unordered_set/operators/1.cc
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/operators/1.cc
@@ -99,8 +99,56 @@ void test01()
  VERIFY( !(us1 != cus2) );
}

+void test02()
+{
+  std::unordered_set<int> us1 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+  std::unordered_set<int> us2 { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 };
+
+  VERIFY( us1 == us2 );
+}
+
+struct Hash
+{
+  std::size_t
+  operator()(const std::pair<int, int>& p) const
+  { return p.first; }
+};
+
+struct Equal
+{
+  bool
+  operator()(const std::pair<int, int>& lhs, const std::pair<int, int>& rhs) 
const
+  { return lhs.first == rhs.first; }
+};
+
+void test03()
+{
+  std::unordered_set<std::pair<int, int>, Hash, Equal> us1
+  {
+    { 0, 0 }, { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 },
+    { 5, 5 }, { 6, 6 }, { 7, 7 }, { 8, 8 }, { 9, 9 }
+  };
+  std::unordered_set<std::pair<int, int>, Hash, Equal> us2
+  {
+    { 5, 5 }, { 6, 6 }, { 7, 7 }, { 8, 8 }, { 9, 9 },
+    { 0, 0 }, { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 }
+  };
+
+  VERIFY( us1 == us2 );
+
+  std::unordered_set<std::pair<int, int>, Hash, Equal> us3
+  {
+    { 5, -5 }, { 6, 6 }, { 7, 7 }, { 8, 8 }, { 9, 9 },
+    { 0, 0  }, { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 }
+  };
+
+  VERIFY( us1 != us3 );
+}
+
int main()
{
  test01();
+  test02();
+  test03();
  return 0;
}

Reply via email to