https://gcc.gnu.org/g:453d42046c461a8aa8ed68a88b94dac2d4dde73b

commit r15-8255-g453d42046c461a8aa8ed68a88b94dac2d4dde73b
Author: Tomasz Kamiński <tkami...@redhat.com>
Date:   Tue Mar 18 11:08:19 2025 +0100

    libstdc++: Add P1206R7 from_range members to unordered maps [PR111055]
    
    This is another piece of P1206R7, adding new members to std::unordered_map
    and std::unordered_multimap.
    
            PR libstdc++/111055
    
    libstdc++-v3/ChangeLog:
    
            * include/bits/unordered_map.h (unordered_map): Define from_range
            constructors and insert_range member.
            (unordered_multimap): Likewise.
            * testsuite/23_containers/unordered_multimap/cons/from_range.cc:
            New test.
            * 
testsuite/23_containers/unordered_multimap/modifiers/insert_range.cc:
            New test.
            * testsuite/23_containers/unordered_map/cons/from_range.cc:
            New test.
            * testsuite/23_containers/unordered_map/modifiers/insert_range.cc:
            New test.
    
    Reviewed-by: Jonathan Wakely <jwak...@redhat.com>
    Signed-off-by: Tomasz Kamiński <tkami...@redhat.com>

Diff:
---
 libstdc++-v3/include/bits/unordered_map.h          | 203 ++++++++++++++++++
 .../23_containers/unordered_map/cons/from_range.cc | 232 ++++++++++++++++++++
 .../unordered_map/modifiers/insert_range.cc        |  79 +++++++
 .../unordered_multimap/cons/from_range.cc          | 238 +++++++++++++++++++++
 .../unordered_multimap/modifiers/insert_range.cc   |  76 +++++++
 5 files changed, 828 insertions(+)

diff --git a/libstdc++-v3/include/bits/unordered_map.h 
b/libstdc++-v3/include/bits/unordered_map.h
index 9f205f7521d3..5c9304871907 100644
--- a/libstdc++-v3/include/bits/unordered_map.h
+++ b/libstdc++-v3/include/bits/unordered_map.h
@@ -34,6 +34,9 @@
 #include <bits/allocator.h>
 #include <bits/functional_hash.h> // hash
 #include <bits/stl_function.h>    // equal_to
+#if __glibcxx_ranges_to_container // C++ >= 23
+# include <bits/ranges_base.h> // ranges::begin, ranges::distance etc.
+#endif
 
 namespace std _GLIBCXX_VISIBILITY(default)
 {
@@ -274,6 +277,42 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
       : unordered_map(__l, __n, __hf, key_equal(), __a)
       { }
 
+#if __glibcxx_ranges_to_container // C++ >= 23
+      /**
+       *  @brief  Builds an %unordered_map from a range.
+       *  @since C++23
+       *  @param  __rg An input range of elements that can be converted to
+       *               the maps's value type.
+       *  @param __n  Minimal initial number of buckets.
+       *  @param __hf  A hash functor.
+       *  @param __eql  A key equality functor.
+       *  @param  __a  An allocator object.
+       *
+       *  Create an %unordered_map consisting of copies of the elements in the
+       *  range. This is linear in N (where N is `std::ranges::size(__rg)`).
+       */
+       template<__detail::__container_compatible_range<value_type> _Rg>
+        unordered_map(from_range_t, _Rg&& __rg,
+                      size_type __n = 0,
+                      const hasher& __hf = hasher(),
+                      const key_equal& __eql = key_equal(),
+                      const allocator_type& __a = allocator_type())
+         : _M_h(__n, __hf, __eql, __a)
+         { insert_range(std::forward<_Rg>(__rg)); }
+
+       template<__detail::__container_compatible_range<value_type> _Rg>
+        unordered_map(from_range_t, _Rg&& __rg, size_type __n,
+                      const allocator_type& __a)
+         : _M_h(__n, hasher(), key_equal(), __a)
+         { insert_range(std::forward<_Rg>(__rg)); }
+
+       template<__detail::__container_compatible_range<value_type> _Rg>
+        unordered_map(from_range_t, _Rg&& __rg, size_type __n,
+                      const hasher& __hf, const allocator_type& __a)
+         : _M_h(__n, __hf, key_equal(), __a)
+         { insert_range(std::forward<_Rg>(__rg)); }
+#endif
+
       /// Copy assignment operator.
       unordered_map&
       operator=(const unordered_map&) = default;
@@ -635,6 +674,23 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
       insert(initializer_list<value_type> __l)
       { _M_h.insert(__l); }
 
+#if __glibcxx_ranges_to_container // C++ >= 23
+      /**
+       *  @brief Inserts a range of elements.
+       *  @since C++23
+       *  @param  __rg An input range of elements that can be converted to
+       *               the map's value type.
+       */
+      template<__detail::__container_compatible_range<value_type> _Rg>
+       void
+       insert_range(_Rg&& __rg)
+       {
+         auto __first = ranges::begin(__rg);
+         const auto __last = ranges::end(__rg);
+         for (; __first != __last; ++__first)
+           _M_h.emplace(*__first);
+       }
+#endif
 
 #ifdef __glibcxx_unordered_map_try_emplace // >= C++17 && HOSTED
       /**
@@ -1228,6 +1284,47 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
                  _Hash, _Allocator)
     -> unordered_map<_Key, _Tp, _Hash, equal_to<_Key>, _Allocator>;
 
+#if __glibcxx_ranges_to_container // C++ >= 23
+  template<ranges::input_range _Rg,
+          __not_allocator_like _Hash = hash<__detail::__range_key_type<_Rg>>,
+          __not_allocator_like _Pred = 
equal_to<__detail::__range_key_type<_Rg>>,
+          __allocator_like _Allocator =
+            allocator<__detail::__range_to_alloc_type<_Rg>>>
+    unordered_map(from_range_t, _Rg&&, unordered_map<int, int>::size_type = {},
+                 _Hash = _Hash(), _Pred = _Pred(), _Allocator = _Allocator())
+    -> unordered_map<__detail::__range_key_type<_Rg>,
+                    __detail::__range_mapped_type<_Rg>,
+                    _Hash, _Pred, _Allocator>;
+
+  template<ranges::input_range _Rg,
+          __allocator_like _Allocator>
+    unordered_map(from_range_t, _Rg&&, unordered_map<int, int>::size_type,
+                 _Allocator)
+    -> unordered_map<__detail::__range_key_type<_Rg>,
+                    __detail::__range_mapped_type<_Rg>,
+                    hash<__detail::__range_key_type<_Rg>>,
+                    equal_to<__detail::__range_key_type<_Rg>>,
+                    _Allocator>;
+
+  template<ranges::input_range _Rg,
+          __allocator_like _Allocator>
+    unordered_map(from_range_t, _Rg&&, _Allocator)
+    -> unordered_map<__detail::__range_key_type<_Rg>,
+                    __detail::__range_mapped_type<_Rg>,
+                    hash<__detail::__range_key_type<_Rg>>,
+                    equal_to<__detail::__range_key_type<_Rg>>,
+                    _Allocator>;
+
+  template<ranges::input_range _Rg,
+          __not_allocator_like _Hash,
+          __allocator_like _Allocator>
+    unordered_map(from_range_t, _Rg&&, unordered_map<int, int>::size_type,
+                 _Hash, _Allocator)
+    -> unordered_map<__detail::__range_key_type<_Rg>,
+                    __detail::__range_mapped_type<_Rg>,
+                    _Hash, equal_to<__detail::__range_key_type<_Rg>>,
+                    _Allocator>;
+#endif
 #endif
 
   /**
@@ -1426,6 +1523,42 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
       : unordered_multimap(__l, __n, __hf, key_equal(), __a)
       { }
 
+#if __glibcxx_ranges_to_container // C++ >= 23
+      /**
+       *  @brief  Builds an %unordered_multimap from a range.
+       *  @since C++23
+       *  @param  __rg An input range of elements that can be converted to
+       *               the maps's value type.
+       *  @param __n  Minimal initial number of buckets.
+       *  @param __hf  A hash functor.
+       *  @param __eql  A key equality functor.
+       *  @param  __a  An allocator object.
+       *
+       *  Create an %unordered_multimap consisting of copies of the elements in
+       *  the range. This is linear in N (where N is 
`std::ranges::size(__rg)`).
+       */
+       template<__detail::__container_compatible_range<value_type> _Rg>
+        unordered_multimap(from_range_t, _Rg&& __rg,
+                           size_type __n = 0,
+                           const hasher& __hf = hasher(),
+                           const key_equal& __eql = key_equal(),
+                           const allocator_type& __a = allocator_type())
+         : _M_h(__n, __hf, __eql, __a)
+         { insert_range(std::forward<_Rg>(__rg)); }
+
+       template<__detail::__container_compatible_range<value_type> _Rg>
+        unordered_multimap(from_range_t, _Rg&& __rg, size_type __n,
+                           const allocator_type& __a)
+         : _M_h(__n, hasher(), key_equal(), __a)
+         { insert_range(std::forward<_Rg>(__rg)); }
+
+       template<__detail::__container_compatible_range<value_type> _Rg>
+        unordered_multimap(from_range_t, _Rg&& __rg, size_type __n,
+                           const hasher& __hf, const allocator_type& __a)
+         : _M_h(__n, __hf, key_equal(), __a)
+         { insert_range(std::forward<_Rg>(__rg)); }
+#endif
+
       /// Copy assignment operator.
       unordered_multimap&
       operator=(const unordered_multimap&) = default;
@@ -1655,6 +1788,32 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
       insert(initializer_list<value_type> __l)
       { _M_h.insert(__l); }
 
+#if __glibcxx_ranges_to_container // C++ >= 23
+      /**
+       *  @brief Inserts a range of elements.
+       *  @since C++23
+       *  @param  __rg An input range of elements that can be converted to
+       *               the maps's value type.
+       */
+      template<__detail::__container_compatible_range<value_type> _Rg>
+       void
+       insert_range(_Rg&& __rg)
+       {
+         auto __first = ranges::begin(__rg);
+         const auto __last = ranges::end(__rg);
+         if (__first == __last)
+           return;
+
+         if constexpr (ranges::forward_range<_Rg> || ranges::sized_range<_Rg>)
+           _M_h._M_rehash_insert(ranges::distance(__rg));
+         else
+           _M_h._M_rehash_insert(1);
+
+         for (; __first != __last; ++__first)
+           _M_h.emplace(*__first);
+       }
+#endif
+
 #ifdef __glibcxx_node_extract // >= C++17 && HOSTED
       /// Extract a node.
       node_type
@@ -2138,6 +2297,50 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
                       _Hash, _Allocator)
     -> unordered_multimap<_Key, _Tp, _Hash, equal_to<_Key>, _Allocator>;
 
+#if __glibcxx_ranges_to_container // C++ >= 23
+  template<ranges::input_range _Rg,
+          __not_allocator_like _Hash = hash<__detail::__range_key_type<_Rg>>,
+          __not_allocator_like _Pred = 
equal_to<__detail::__range_key_type<_Rg>>,
+          __allocator_like _Allocator =
+            allocator<__detail::__range_to_alloc_type<_Rg>>>
+    unordered_multimap(from_range_t, _Rg&&,
+                      unordered_multimap<int, int>::size_type = {},
+                      _Hash = _Hash(), _Pred = _Pred(),
+                      _Allocator = _Allocator())
+    -> unordered_multimap<__detail::__range_key_type<_Rg>,
+                         __detail::__range_mapped_type<_Rg>,
+                         _Hash, _Pred, _Allocator>;
+
+  template<ranges::input_range _Rg,
+          __allocator_like _Allocator>
+    unordered_multimap(from_range_t, _Rg&&, unordered_multimap<int, 
int>::size_type,
+                 _Allocator)
+    -> unordered_multimap<__detail::__range_key_type<_Rg>,
+                         __detail::__range_mapped_type<_Rg>,
+                         hash<__detail::__range_key_type<_Rg>>,
+                         equal_to<__detail::__range_key_type<_Rg>>,
+                         _Allocator>;
+
+  template<ranges::input_range _Rg,
+          __allocator_like _Allocator>
+    unordered_multimap(from_range_t, _Rg&&, _Allocator)
+    -> unordered_multimap<__detail::__range_key_type<_Rg>,
+                         __detail::__range_mapped_type<_Rg>,
+                         hash<__detail::__range_key_type<_Rg>>,
+                         equal_to<__detail::__range_key_type<_Rg>>,
+                         _Allocator>;
+
+  template<ranges::input_range _Rg,
+          __not_allocator_like _Hash,
+          __allocator_like _Allocator>
+    unordered_multimap(from_range_t, _Rg&&,
+                      unordered_multimap<int, int>::size_type,
+                      _Hash, _Allocator)
+    -> unordered_multimap<__detail::__range_key_type<_Rg>,
+                         __detail::__range_mapped_type<_Rg>,
+                         _Hash, equal_to<__detail::__range_key_type<_Rg>>,
+                         _Allocator>;
+#endif
 #endif
 
   template<class _Key, class _Tp, class _Hash, class _Pred, class _Alloc>
diff --git 
a/libstdc++-v3/testsuite/23_containers/unordered_map/cons/from_range.cc 
b/libstdc++-v3/testsuite/23_containers/unordered_map/cons/from_range.cc
new file mode 100644
index 000000000000..b3cbb2e60625
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/cons/from_range.cc
@@ -0,0 +1,232 @@
+// { dg-do run { target c++23 } }
+
+#include <algorithm>
+#include <unordered_map>
+#include <span>
+#include <testsuite_allocator.h>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+
+struct StateHash {
+  int state = 17;
+
+  template<typename T>
+  size_t operator()(T const& t) const {
+    return std::hash<T>()(t) + 43;
+  }
+};
+
+struct StateEq {
+  int state = 7;
+
+  template<typename T, typename U>
+  bool operator()(T const& l, U const & r) const {
+    return l == r;
+  }
+};
+
+void
+test_deduction_guide()
+{
+  __gnu_test::test_input_range<std::pair<long, float>> r(0, 0);
+  std::unordered_map m(std::from_range, r);
+  static_assert(std::is_same_v<decltype(m), std::unordered_map<long, float>>);
+
+  std::unordered_map m2(std::from_range, r, 0);
+  static_assert(std::is_same_v<decltype(m2), std::unordered_map<long, float>>);
+
+  StateHash hf;
+  std::unordered_map m3(std::from_range, r, 0, hf);
+  static_assert(std::is_same_v<
+    decltype(m3),
+    std::unordered_map<long, float, StateHash>>);
+
+  StateEq eq;
+  std::unordered_map m4(std::from_range, r, 0, hf, eq);
+  static_assert(std::is_same_v<
+    decltype(m4),
+    std::unordered_map<long, float, StateHash, StateEq>>);
+
+  using Alloc = __gnu_test::SimpleAllocator<std::pair<const long, float>>;
+  Alloc alloc;
+  // LWG2713: there is no matching constructor
+  // std::unordered_map m5(std::from_range, r, alloc);
+  // static_assert(std::is_same_v<
+  //   decltype(m5),
+  //   std::unordered_map<long, float,
+  //                      std::hash<long>, std::equal_to<long>, Alloc>>);
+
+  std::unordered_map m6(std::from_range, r, 0, alloc);
+  static_assert(std::is_same_v<
+    decltype(m6),
+    std::unordered_map<long, float,
+                      std::hash<long>, std::equal_to<long>, Alloc>>);
+
+  std::unordered_map m7(std::from_range, r, 0, hf, alloc);
+  static_assert(std::is_same_v<
+    decltype(m7),
+    std::unordered_map<long, float, StateHash, std::equal_to<long>, Alloc>>);
+
+  std::unordered_map m8(std::from_range, r, 0, hf, eq, alloc);
+  static_assert(std::is_same_v<
+    decltype(m8),
+    std::unordered_map<long, float, StateHash, StateEq, Alloc>>);
+
+  __gnu_test::test_input_range<std::pair<const long, const float>> r2(0, 0);
+  std::unordered_map m9(std::from_range, r2);
+  static_assert(std::is_same_v<
+    decltype(m9),
+    std::unordered_map<long, const float>>);
+
+  // LWG4223: deduces map<const long&, float&>
+  // __gnu_test::test_input_range<std::pair<const long&, float&>> r3(0, 0);
+  // std::unordered_map m10(std::from_range, r3);
+
+  // LWG4223: no deduction guide
+  // __gnu_test::test_input_range<std::tuple<long, float>> r4(0, 0);
+  // std::unordered_map m11(std::from_range, r4);
+}
+
+template<typename T, typename U>
+constexpr bool is_equal(std::hash<T>, std::hash<U>)
+{ return true; }
+
+template<typename T, typename U>
+constexpr bool is_equal(std::equal_to<T>, std::equal_to<U>)
+{ return true; }
+
+constexpr bool is_equal(StateHash lhs, StateHash rhs)
+{ return lhs.state = rhs.state; }
+
+constexpr bool is_equal(StateEq lhs, StateEq rhs)
+{ return lhs.state = rhs.state; }
+
+template<typename Range, typename Alloc, typename Hash, typename Equal>
+constexpr void
+do_test(Alloc alloc, Hash hf, Equal eqf)
+{
+  // The map's value_type, key_type and mapped_type.
+  using P = typename Alloc::value_type;
+  using K = typename P::first_type;
+  using V = typename P::second_type;
+
+  // The range's value_type.
+  using T = std::ranges::range_value_t<Range>;
+  T a[]{{1,2},{2,3},{3,4},{4,5},{5,6},{6,7},{7,8},{8,9},{9,0},
+       {1,1},{2,2},{3,3},{4,4},{5,5}};
+
+  auto eq = [&](std::unordered_map<K, V, Hash, Equal, Alloc> const& l,
+               std::span<T> r) {
+    if (l.size() != r.size())
+      return false;
+
+    return std::ranges::is_permutation(l, r);
+  };
+
+  std::unordered_map<K, V, Hash, Equal, Alloc>
+    m0(std::from_range, Range(a, a+0));
+  VERIFY( m0.empty() );
+  VERIFY( is_equal(m0.hash_function(), Hash()) );
+  VERIFY( is_equal(m0.key_eq(), Equal()) );
+  VERIFY( m0.get_allocator() == Alloc() );
+
+  std::unordered_map<K, V, Hash, Equal, Alloc>
+    m4(std::from_range, Range(a, a+4), 2);
+  VERIFY( eq(m4, {a, 4}) );
+  VERIFY( m4.bucket_count() >= 2 );
+  VERIFY( is_equal(m4.hash_function(), Hash()) );
+  VERIFY( is_equal(m4.key_eq(), Equal()) );
+  VERIFY( m4.get_allocator() == Alloc() );
+
+  std::unordered_map<K, V, Hash, Equal, Alloc>
+    m7(std::from_range, Range(a, a+7), 3, hf);
+  VERIFY( eq(m7, {a, 7}) );
+  VERIFY( m7.bucket_count() >= 3 );
+  VERIFY( is_equal(m7.hash_function(), hf) );
+  VERIFY( is_equal(m7.key_eq(), Equal()) );
+  VERIFY( m7.get_allocator() == Alloc() );
+
+  std::unordered_map<K, V, Hash, Equal, Alloc>
+    m9(std::from_range, Range(a, a+9), 5, hf, eqf);
+  VERIFY( eq(m9, {a, 9}) );
+  VERIFY( m9.bucket_count() >= 5 );
+  VERIFY( m9.get_allocator() == Alloc() );
+  VERIFY( is_equal(m9.hash_function(), hf) );
+  VERIFY( is_equal(m9.key_eq(), eqf) );
+
+  // LWG2713: there is no matching constructor
+  // std::unordered_map<K, V, Hash, Equal, Alloc>
+  //   ma1(std::from_range, Range(a, a+14), alloc);
+  // VERIFY( eq(ma1, {a, 9}) );
+  // VERIFY( is_equal(ma1.hash_function(), Hash()) );
+  // VERIFY( is_equal(ma1.key_eq(), Equal()) );
+  // VERIFY( ma1.get_allocator() == alloc );
+
+  std::unordered_map<K, V, Hash, Equal, Alloc>
+    ma2(std::from_range, Range(a, a+14), 2, alloc);
+  VERIFY( eq(ma2, {a, 9}) );
+  VERIFY( ma2.bucket_count() >= 2 );
+  VERIFY( is_equal(ma2.hash_function(), Hash()) );
+  VERIFY( is_equal(ma2.key_eq(), Equal()) );
+  VERIFY( ma2.get_allocator() == alloc );
+
+  std::unordered_map<K, V, Hash, Equal, Alloc>
+    ma3(std::from_range, Range(a, a+14), 3, hf, alloc);
+  VERIFY( eq(ma3, {a, 9}) );
+  VERIFY( ma3.bucket_count() >= 3 );
+  VERIFY( is_equal(ma3.hash_function(), hf) );
+  VERIFY( is_equal(ma3.key_eq(), Equal()) );
+  VERIFY( ma3.get_allocator() == alloc );
+
+  std::unordered_map<K, V, Hash, Equal, Alloc>
+    ma4(std::from_range, Range(a, a+14), 5, hf, eqf, alloc);
+  VERIFY( eq(ma4, {a, 9}) );
+  VERIFY( ma4.bucket_count() >= 5 );
+  VERIFY( is_equal(ma4.hash_function(), hf) );
+  VERIFY( is_equal(ma4.key_eq(), eqf) );
+  VERIFY( ma4.get_allocator() == alloc );
+}
+
+template<typename Range>
+void
+do_test_ahe()
+{
+  do_test<Range>(std::allocator<std::pair<const int, double>>(),
+                std::hash<int>(), std::equal_to<int>());
+  do_test<Range>(std::allocator<std::pair<const int, double>>(),
+                StateHash{27}, StateEq{17});
+  do_test<Range>(__gnu_test::uneq_allocator<std::pair<const int, double>>(42),
+                std::hash<int>(), std::equal_to<int>());
+  do_test<Range>(__gnu_test::uneq_allocator<std::pair<const int, double>>(42),
+                StateHash{27}, StateEq{17});
+}
+
+struct MyPair {
+  long x;
+  long y;
+
+  constexpr operator std::pair<int const, double>() const
+  { return {x, y}; }
+
+  friend bool operator==(MyPair, MyPair) = default;
+  constexpr friend bool operator==(MyPair lhs, std::pair<int const, double> 
rhs)
+  { return (lhs.x == rhs.first) && (lhs.y == rhs.second); }
+};
+
+bool
+test_ranges()
+{
+  using namespace __gnu_test;
+
+  do_test_ahe<test_forward_range<std::pair<int, double>>>();
+  do_test_ahe<test_forward_range<std::pair<short, float>>>();
+  do_test_ahe<test_forward_range<std::tuple<int, double>>>();
+  do_test_ahe<test_forward_range<MyPair>>();
+
+  return true;
+}
+
+int main()
+{
+  test_ranges();
+}
diff --git 
a/libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/insert_range.cc 
b/libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/insert_range.cc
new file mode 100644
index 000000000000..41be2fdc1961
--- /dev/null
+++ 
b/libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/insert_range.cc
@@ -0,0 +1,79 @@
+// { dg-do run { target c++23 } }
+
+#include <algorithm>
+#include <unordered_map>
+#include <span>
+#include <testsuite_allocator.h>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+
+template<typename Range, typename K, typename V>
+constexpr void
+do_test()
+{
+  // The range's value_type.
+  using T = std::ranges::range_value_t<Range>;
+  T a[]{{1,2},{2,3},{3,4},{4,5},{5,6},{6,7},{7,8},{8,9},{9,0},
+       {1,1},{2,2},{3,3},{4,4},{5,5}};
+
+  auto eq = [&](std::unordered_map<K, V> const& l,
+               std::span<T> r) {
+    if (l.size() != r.size())
+      return false;
+
+    return std::ranges::is_permutation(l, r);
+  };
+
+  std::unordered_map<K, V> m;
+  m.insert_range(Range(a, a+0));
+  VERIFY( m.empty() );
+
+  m.insert_range(Range(a, a+4));
+  VERIFY( eq(m, {a, 4}) );
+
+  m.insert_range(Range(a+4, a+7));
+  VERIFY( eq(m, {a, 7}) );
+
+  m.insert_range(Range(a, a+9));
+  VERIFY( eq(m, {a, 9}) );
+
+  m.insert_range(Range(a, a+14));
+  VERIFY( eq(m, {a, 9}) );
+}
+
+struct MyPair {
+  long x;
+  long y;
+
+  constexpr operator std::pair<int const, double>() const
+  { return {x, y}; }
+
+  friend bool operator==(MyPair, MyPair) = default;
+  constexpr friend bool operator==(MyPair lhs, std::pair<int const, double> 
rhs)
+  { return (lhs.x == rhs.first) && (lhs.y == rhs.second); }
+};
+
+template<typename Range>
+void
+do_test_v()
+{
+  do_test<Range, int, double>();
+}
+
+bool
+test_ranges()
+{
+  using namespace __gnu_test;
+
+  do_test_v<test_forward_range<std::pair<int, double>>>();
+  do_test_v<test_forward_range<std::pair<short, float>>>();
+  do_test_v<test_forward_range<std::tuple<int, double>>>();
+  do_test_v<test_forward_range<MyPair>>();
+
+  return true;
+}
+
+int main()
+{
+  test_ranges();
+}
diff --git 
a/libstdc++-v3/testsuite/23_containers/unordered_multimap/cons/from_range.cc 
b/libstdc++-v3/testsuite/23_containers/unordered_multimap/cons/from_range.cc
new file mode 100644
index 000000000000..9273ef0d57a3
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/cons/from_range.cc
@@ -0,0 +1,238 @@
+// { dg-do run { target c++23 } }
+
+#include <algorithm>
+#include <unordered_map>
+#include <span>
+#include <testsuite_allocator.h>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+
+struct StateHash {
+  int state = 17;
+
+  template<typename T>
+  size_t operator()(T const& t) const {
+    return std::hash<T>()(t) + 43;
+  }
+};
+
+struct StateEq {
+  int state = 7;
+
+  template<typename T, typename U>
+  bool operator()(T const& l, U const & r) const {
+    return l == r;
+  }
+};
+
+void
+test_deduction_guide()
+{
+  __gnu_test::test_input_range<std::pair<long, float>> r(0, 0);
+  std::unordered_multimap m(std::from_range, r);
+  static_assert(std::is_same_v<
+    decltype(m),
+    std::unordered_multimap<long, float>>);
+
+  std::unordered_multimap m2(std::from_range, r, 0);
+  static_assert(std::is_same_v<
+    decltype(m2),
+    std::unordered_multimap<long, float>>);
+
+  StateHash hf;
+  std::unordered_multimap m3(std::from_range, r, 0, hf);
+  static_assert(std::is_same_v<
+    decltype(m3),
+    std::unordered_multimap<long, float, StateHash>>);
+
+  StateEq eq;
+  std::unordered_multimap m4(std::from_range, r, 0, hf, eq);
+  static_assert(std::is_same_v<
+    decltype(m4),
+    std::unordered_multimap<long, float, StateHash, StateEq>>);
+
+  using Alloc = __gnu_test::SimpleAllocator<std::pair<const long, float>>;
+  Alloc alloc;
+  // LWG2713: there is no matching constructor
+  // std::unordered_multimap m5(std::from_range, r, alloc);
+  // static_assert(std::is_same_v<
+  //   decltype(m5),
+  //   std::unordered_multimap<long, float,
+  //                           std::hash<long>, std::equal_to<long>, Alloc>>);
+
+  std::unordered_multimap m6(std::from_range, r, 0, alloc);
+  static_assert(std::is_same_v<
+    decltype(m6),
+    std::unordered_multimap<long, float,
+                           std::hash<long>, std::equal_to<long>, Alloc>>);
+
+  std::unordered_multimap m7(std::from_range, r, 0, hf, alloc);
+  static_assert(std::is_same_v<
+    decltype(m7),
+    std::unordered_multimap<long, float,
+                           StateHash, std::equal_to<long>, Alloc>>);
+
+  std::unordered_multimap m8(std::from_range, r, 0, hf, eq, alloc);
+  static_assert(std::is_same_v<
+    decltype(m8),
+    std::unordered_multimap<long, float, StateHash, StateEq, Alloc>>);
+
+  __gnu_test::test_input_range<std::pair<const long, const float>> r2(0, 0);
+  std::unordered_multimap m9(std::from_range, r2);
+  static_assert(std::is_same_v<
+    decltype(m9),
+    std::unordered_multimap<long, const float>>);
+
+  // LWG4223: deduces map<const long&, float&>
+  // __gnu_test::test_input_range<std::pair<const long&, float&>> r3(0, 0);
+  // std::unordered_multimap m10(std::from_range, r3);
+
+  // LWG4223: no deduction guide
+  // __gnu_test::test_input_range<std::tuple<long, float>> r4(0, 0);
+  // std::unordered_multimap m11(std::from_range, r4);
+}
+
+template<typename T, typename U>
+constexpr bool is_equal(std::hash<T>, std::hash<U>)
+{ return true; }
+
+template<typename T, typename U>
+constexpr bool is_equal(std::equal_to<T>, std::equal_to<U>)
+{ return true; }
+
+constexpr bool is_equal(StateHash lhs, StateHash rhs)
+{ return lhs.state = rhs.state; }
+
+constexpr bool is_equal(StateEq lhs, StateEq rhs)
+{ return lhs.state = rhs.state; }
+
+template<typename Range, typename Alloc, typename Hash, typename Equal>
+constexpr void
+do_test(Alloc alloc, Hash hf, Equal eqf)
+{
+  // The map's value_type, key_type and mapped_type.
+  using P = typename Alloc::value_type;
+  using K = typename P::first_type;
+  using V = typename P::second_type;
+
+  // The range's value_type.
+  using T = std::ranges::range_value_t<Range>;
+  T a[]{{1,2},{2,3},{3,4},{4,5},{5,6},{6,7},{7,8},{8,9},{9,0},
+       {1,1},{2,2},{3,3},{4,4},{5,5}};
+
+  auto eq = [&](std::unordered_multimap<K, V, Hash, Equal, Alloc> const& l,
+               std::span<T> r) {
+    if (l.size() != r.size())
+      return false;
+
+    return std::ranges::is_permutation(l, r);
+  };
+
+  std::unordered_multimap<K, V, Hash, Equal, Alloc>
+    m0(std::from_range, Range(a, a+0));
+  VERIFY( m0.empty() );
+  VERIFY( is_equal(m0.hash_function(), Hash()) );
+  VERIFY( is_equal(m0.key_eq(), Equal()) );
+  VERIFY( m0.get_allocator() == Alloc() );
+
+  std::unordered_multimap<K, V, Hash, Equal, Alloc>
+    m4(std::from_range, Range(a, a+4), 2);
+  VERIFY( eq(m4, {a, 4}) );
+  VERIFY( m4.bucket_count() >= 2 );
+  VERIFY( is_equal(m4.hash_function(), Hash()) );
+  VERIFY( is_equal(m4.key_eq(), Equal()) );
+  VERIFY( m4.get_allocator() == Alloc() );
+
+  std::unordered_multimap<K, V, Hash, Equal, Alloc>
+    m7(std::from_range, Range(a, a+7), 3, hf);
+  VERIFY( eq(m7, {a, 7}) );
+  VERIFY( m7.bucket_count() >= 3 );
+  VERIFY( is_equal(m7.hash_function(), hf) );
+  VERIFY( is_equal(m7.key_eq(), Equal()) );
+  VERIFY( m7.get_allocator() == Alloc() );
+
+  std::unordered_multimap<K, V, Hash, Equal, Alloc>
+    m9(std::from_range, Range(a, a+9), 5, hf, eqf);
+  VERIFY( eq(m9, {a, 9}) );
+  VERIFY( m9.bucket_count() >= 5 );
+  VERIFY( m9.get_allocator() == Alloc() );
+  VERIFY( is_equal(m9.hash_function(), hf) );
+  VERIFY( is_equal(m9.key_eq(), eqf) );
+
+  // LWG2713: there is no matching constructor
+  // std::unordered_multimap<K, V, Hash, Equal, Alloc>
+  //   ma1(std::from_range, Range(a, a+14), alloc);
+  // VERIFY( eq(ma1, {a, 14}) );
+  // VERIFY( is_equal(ma1.hash_function(), Hash()) );
+  // VERIFY( is_equal(ma1.key_eq(), Equal()) );
+  // VERIFY( ma1.get_allocator() == alloc );
+
+  std::unordered_multimap<K, V, Hash, Equal, Alloc>
+    ma2(std::from_range, Range(a, a+14), 2, alloc);
+  VERIFY( eq(ma2, {a, 14}) );
+  VERIFY( ma2.bucket_count() >= 2 );
+  VERIFY( is_equal(ma2.hash_function(), Hash()) );
+  VERIFY( is_equal(ma2.key_eq(), Equal()) );
+  VERIFY( ma2.get_allocator() == alloc );
+
+  std::unordered_multimap<K, V, Hash, Equal, Alloc>
+    ma3(std::from_range, Range(a, a+14), 3, hf, alloc);
+  VERIFY( eq(ma3, {a, 14}) );
+  VERIFY( ma3.bucket_count() >= 3 );
+  VERIFY( is_equal(ma3.hash_function(), hf) );
+  VERIFY( is_equal(ma3.key_eq(), Equal()) );
+  VERIFY( ma3.get_allocator() == alloc );
+
+  std::unordered_multimap<K, V, Hash, Equal, Alloc>
+    ma4(std::from_range, Range(a, a+14), 5, hf, eqf, alloc);
+  VERIFY( eq(ma4, {a, 14}) );
+  VERIFY( ma4.bucket_count() >= 5 );
+  VERIFY( is_equal(ma4.hash_function(), hf) );
+  VERIFY( is_equal(ma4.key_eq(), eqf) );
+  VERIFY( ma4.get_allocator() == alloc );
+}
+
+template<typename Range>
+void
+do_test_ahe()
+{
+  do_test<Range>(std::allocator<std::pair<const int, double>>(),
+                std::hash<int>(), std::equal_to<int>());
+  do_test<Range>(std::allocator<std::pair<const int, double>>(),
+                StateHash{27}, StateEq{17});
+  do_test<Range>(__gnu_test::uneq_allocator<std::pair<const int, double>>(42),
+                std::hash<int>(), std::equal_to<int>());
+  do_test<Range>(__gnu_test::uneq_allocator<std::pair<const int, double>>(42),
+                StateHash{27}, StateEq{17});
+}
+
+struct MyPair {
+  long x;
+  long y;
+
+  constexpr operator std::pair<int const, double>() const
+  { return {x, y}; }
+
+  friend bool operator==(MyPair, MyPair) = default;
+  constexpr friend bool operator==(MyPair lhs, std::pair<int const, double> 
rhs)
+  { return (lhs.x == rhs.first) && (lhs.y == rhs.second); }
+};
+
+bool
+test_ranges()
+{
+  using namespace __gnu_test;
+
+  do_test_ahe<test_forward_range<std::pair<int, double>>>();
+  do_test_ahe<test_range_nocopy<std::pair<int, double>, 
input_iterator_wrapper_nocopy>>();
+  do_test_ahe<test_forward_range<std::pair<short, float>>>();
+  do_test_ahe<test_forward_range<std::tuple<int, double>>>();
+  do_test_ahe<test_forward_range<MyPair>>();
+
+  return true;
+}
+
+int main()
+{
+  test_ranges();
+}
diff --git 
a/libstdc++-v3/testsuite/23_containers/unordered_multimap/modifiers/insert_range.cc
 
b/libstdc++-v3/testsuite/23_containers/unordered_multimap/modifiers/insert_range.cc
new file mode 100644
index 000000000000..30503e51fd77
--- /dev/null
+++ 
b/libstdc++-v3/testsuite/23_containers/unordered_multimap/modifiers/insert_range.cc
@@ -0,0 +1,76 @@
+// { dg-do run { target c++23 } }
+
+#include <algorithm>
+#include <unordered_map>
+#include <span>
+#include <testsuite_allocator.h>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+
+template<typename Range, typename K, typename V>
+constexpr void
+do_test()
+{
+  // The range's value_type.
+  using T = std::ranges::range_value_t<Range>;
+  T a[]{{1,2},{2,3},{3,4},{4,5},{5,6},{6,7},{7,8},{8,9},{9,0},
+       {1,1},{2,2},{3,3},{4,4},{5,5}};
+
+  auto eq = [&](std::unordered_multimap<K, V> const& l,
+               std::span<T> r) {
+    if (l.size() != r.size())
+      return false;
+
+    return std::ranges::is_permutation(l, r);
+  };
+
+  std::unordered_multimap<K, V> m;
+  m.insert_range(Range(a, a+0));
+  VERIFY( m.empty() );
+
+  m.insert_range(Range(a, a+4));
+  VERIFY( eq(m, {a, 4}) );
+
+  m.insert_range(Range(a+4, a+9));
+  VERIFY( eq(m, {a, 9}) );
+
+  m.insert_range(Range(a+9, a+14));
+  VERIFY( eq(m, {a, 14}) );
+}
+
+struct MyPair {
+  long x;
+  long y;
+
+  constexpr operator std::pair<int const, double>() const
+  { return {x, y}; }
+
+  friend bool operator==(MyPair, MyPair) = default;
+  constexpr friend bool operator==(MyPair lhs, std::pair<int const, double> 
rhs)
+  { return (lhs.x == rhs.first) && (lhs.y == rhs.second); }
+};
+
+template<typename Range>
+void
+do_test_v()
+{
+  do_test<Range, int, double>();
+}
+
+bool
+test_ranges()
+{
+  using namespace __gnu_test;
+
+  do_test_v<test_forward_range<std::pair<int, double>>>();
+  do_test_v<test_forward_range<std::pair<short, float>>>();
+  do_test_v<test_forward_range<std::tuple<int, double>>>();
+  do_test_v<test_forward_range<MyPair>>();
+
+  return true;
+}
+
+int main()
+{
+  test_ranges();
+}

Reply via email to