On 17/03/25 08:59 +0100, Tomasz Kamiński wrote:
This is another piece of P1206R7, adding new members to std::map
and std::multimap.

        PR libstdc++/111055

libstdc++-v3/ChangeLog:

        * include/bits/ranges_base.h (__detail::__range_to_alloc_type):
        Define.
        * include/bits/stl_multimap.h: (inser_range)
        (multimap(from_range_t, _Rg&&, const _Compare&, const _Alloc&))
        (multimap(from_range_t, _Rg&&, const _Alloc&)): Define.
        * include/bits/stl_map.h: (map(from_range_t, _Rg&&, const _Alloc&)) i
        (map(from_range_t, _Rg&&, const _Compare&, const _Alloc&), 
insert_range):
        Define.
        * testsuite/23_containers/multimap/cons/from_range.cc: New test.
        * testsuite/23_containers/multimap/modifiers/insert/insert_range.cc: 
New test.
        * testsuite/23_containers/map/cons/from_range.cc: New test.
        * testsuite/23_containers/map/modifiers/insert/insert_range.cc: New 
test.
---
Updated includes, replaced spaces with tabs, and mentioned LWG4223.
Tested on x86_64-linux without PCH. OK for trunk?

libstdc++-v3/include/bits/ranges_base.h       |   7 +
libstdc++-v3/include/bits/stl_map.h           |  59 +++++++
libstdc++-v3/include/bits/stl_multimap.h      |  60 +++++++
.../23_containers/map/cons/from_range.cc      | 155 ++++++++++++++++++
.../map/modifiers/insert/insert_range.cc      | 102 ++++++++++++
.../23_containers/multimap/cons/from_range.cc | 155 ++++++++++++++++++
.../multimap/modifiers/insert/insert_range.cc |  99 +++++++++++
7 files changed, 637 insertions(+)
create mode 100644 libstdc++-v3/testsuite/23_containers/map/cons/from_range.cc
create mode 100644 
libstdc++-v3/testsuite/23_containers/map/modifiers/insert/insert_range.cc
create mode 100644 
libstdc++-v3/testsuite/23_containers/multimap/cons/from_range.cc
create mode 100644 
libstdc++-v3/testsuite/23_containers/multimap/modifiers/insert/insert_range.cc

diff --git a/libstdc++-v3/include/bits/ranges_base.h 
b/libstdc++-v3/include/bits/ranges_base.h
index 516d04afdab..7f03fe0f463 100644
--- a/libstdc++-v3/include/bits/ranges_base.h
+++ b/libstdc++-v3/include/bits/ranges_base.h
@@ -1083,6 +1083,9 @@ namespace ranges
  inline constexpr from_range_t from_range{};

/// @cond undocumented
+  template<typename _T1, typename _T2>
+    struct pair;
+
namespace __detail
{
  template<typename _Rg, typename _Tp>
@@ -1097,6 +1100,10 @@ namespace __detail
  template<ranges::input_range _Range>
    using __range_mapped_type
      = typename ranges::range_value_t<_Range>::second_type;
+
+  template<ranges::input_range _Range>
+    using __range_to_alloc_type
+      = pair<const __range_key_type<_Range>, __range_mapped_type<_Range>>;

Ugh, why does the standard call this range-to-alloc-type when it gives
us the *allocatED* type, not an *allocatOR* type. And why does this
alias have "to" in the name when range-key-type and range-mapped-type
don't. What were we thinking?!

Please add a comment above this alias saying something like:

    // The allocator's value_type for map-like containers.

Alternatively, we could deviate from the standard and make the alias
actually define the allocator specialization, not its value_type:

  template<ranges::input_range _Range>
    using __range_to_alloc_type
      = allocator<pair<const __range_key_type<_Range>, 
__range_mapped_type<_Range>>>;

But being consistent with the name and meaning of the standard's
range-to-alloc-type seems easier to understand in the long term.

}
/// @endcond
#endif
diff --git a/libstdc++-v3/include/bits/stl_map.h 
b/libstdc++-v3/include/bits/stl_map.h
index d2d0b524cce..3ebadd79218 100644
--- a/libstdc++-v3/include/bits/stl_map.h
+++ b/libstdc++-v3/include/bits/stl_map.h
@@ -62,6 +62,9 @@
#include <initializer_list>
#include <tuple>
#endif
+#if __glibcxx_ranges_to_container // C++ >= 23
+# include <bits/ranges_base.h> // ranges::begin, ranges::distance etc.
+#endif

namespace std _GLIBCXX_VISIBILITY(default)
{
@@ -305,6 +308,26 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
        : _M_t(__comp, _Pair_alloc_type(__a))
        { _M_t._M_insert_range_unique(__first, __last); }

+#if __glibcxx_ranges_to_container // C++ >= 23
+      /**
+       * @brief Builds a %map from a range.
+       * @since C++23
+       */
+      template<__detail::__container_compatible_range<value_type> _Rg>
+       map(from_range_t, _Rg&& __rg,
+           const _Compare& __comp,
+           const _Alloc& __a = _Alloc())
+       : _M_t(__comp, _Pair_alloc_type(__a))
+       { insert_range(std::forward<_Rg>(__rg)); }
+
+      /// Allocator-extended range constructor.
+      template<__detail::__container_compatible_range<value_type> _Rg>
+       map(from_range_t, _Rg&& __rg, const _Alloc& __a = _Alloc())
+       : _M_t(_Pair_alloc_type(__a))
+       { insert_range(std::forward<_Rg>(__rg)); }
+#endif
+
+
#if __cplusplus >= 201103L
      /**
       *  The dtor only erases the elements, and note that if the elements
@@ -880,6 +903,24 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      { insert(__list.begin(), __list.end()); }
#endif

+#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 list'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)
+           insert(*__first);

Do we want emplace(*__first) here to avoid a temporary?

The requirement for assoc.insert_range(rg) is that value_type is
Cpp17EmplaceConstructible into map from *__first, but this will
require is_convertible<range_reference_t<R>, value_type> and
Cpp17MoveInsertable.

+       }
+#endif
+
      /**
       *  @brief Attempts to insert a std::pair into the %map.
       *  @param  __position  An iterator that serves as a hint as to where the
@@ -1495,6 +1536,24 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
    map(initializer_list<pair<_Key, _Tp>>, _Allocator)
    -> map<_Key, _Tp, less<_Key>, _Allocator>;

+#if __glibcxx_ranges_to_container // C++ >= 23
+  template<ranges::input_range _Rg,
+          __not_allocator_like _Compare = 
less<__detail::__range_key_type<_Rg>>,
+          __allocator_like _Alloc =
+             std::allocator<__detail::__range_to_alloc_type<_Rg>>>
+    map(from_range_t, _Rg&&, _Compare = _Compare(), _Alloc = _Alloc())
+      -> map<__detail::__range_key_type<_Rg>,
+            __detail::__range_mapped_type<_Rg>,
+            _Compare, _Alloc>;
+
+  template<ranges::input_range _Rg, __allocator_like _Alloc>
+    map(from_range_t, _Rg&&, _Alloc)
+      -> map<__detail::__range_key_type<_Rg>,
+            __detail::__range_mapped_type<_Rg>,
+            less<__detail::__range_key_type<_Rg>>,
+            _Alloc>;
+#endif
+
#endif // deduction guides

  /**
diff --git a/libstdc++-v3/include/bits/stl_multimap.h 
b/libstdc++-v3/include/bits/stl_multimap.h
index 661d870fd01..b9a28abc919 100644
--- a/libstdc++-v3/include/bits/stl_multimap.h
+++ b/libstdc++-v3/include/bits/stl_multimap.h
@@ -60,6 +60,9 @@
#if __cplusplus >= 201103L
#include <initializer_list>
#endif
+#if __glibcxx_ranges_to_container // C++ >= 23
+# include <bits/ranges_base.h> // ranges::begin, ranges::distance etc.
+#endif

namespace std _GLIBCXX_VISIBILITY(default)
{
@@ -294,6 +297,26 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
        : _M_t(__comp, _Pair_alloc_type(__a))
        { _M_t._M_insert_range_equal(__first, __last); }

+#if __glibcxx_ranges_to_container // C++ >= 23
+      /**
+       * @brief Builds a %map from a range.

s/map/multimap/

I don't think the % character is actually needed here for Doxygen now.
I think at some point in the past it would auto-link 'map' to the
class, which wasn't wanted here. %map prevents that. But I don't think
that auto-linking is enabled by our Doxyfile config nowadays. But for
consistency with the surrounding comments, let's keep it and then
consider cleaning it all up at once at some point (now+years::max()).

+       * @since C++23
+       */
+      template<__detail::__container_compatible_range<value_type> _Rg>
+       multimap(from_range_t, _Rg&& __rg,
+                const _Compare& __comp,
+                const _Alloc& __a = _Alloc())
+       : _M_t(__comp, _Pair_alloc_type(__a))
+       { insert_range(std::forward<_Rg>(__rg)); }
+
+      /// Allocator-extended range constructor.
+      template<__detail::__container_compatible_range<value_type> _Rg>
+       multimap(from_range_t, _Rg&& __rg, const _Alloc& __a = _Alloc())
+       : _M_t(_Pair_alloc_type(__a))
+       { insert_range(std::forward<_Rg>(__rg)); }
+#endif
+
+
#if __cplusplus >= 201103L
      /**
       *  The dtor only erases the elements, and note that if the elements
@@ -632,6 +655,25 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      { this->insert(__l.begin(), __l.end()); }
#endif

+#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 list'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_t._M_emplace_equal(*__first);
+       }
+#endif
+
+
#ifdef __glibcxx_node_extract // >= C++17
      /// Extract a node.
      node_type
@@ -1117,6 +1159,24 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
    multimap(initializer_list<pair<_Key, _Tp>>, _Allocator)
    -> multimap<_Key, _Tp, less<_Key>, _Allocator>;

+#if __glibcxx_ranges_to_container // C++ >= 23
+  template<ranges::input_range _Rg,
+          __not_allocator_like _Compare = 
less<__detail::__range_key_type<_Rg>>,
+          __allocator_like _Alloc =
+             std::allocator<__detail::__range_to_alloc_type<_Rg>>>
+    multimap(from_range_t, _Rg&&, _Compare = _Compare(), _Alloc = _Alloc())
+      -> multimap<__detail::__range_key_type<_Rg>,
+                 __detail::__range_mapped_type<_Rg>,
+                 _Compare, _Alloc>;
+
+  template<ranges::input_range _Rg, __allocator_like _Alloc>
+    multimap(from_range_t, _Rg&&, _Alloc)
+      -> multimap<__detail::__range_key_type<_Rg>,
+                 __detail::__range_mapped_type<_Rg>,
+                 less<__detail::__range_key_type<_Rg>>,
+                 _Alloc>;
+#endif
+
#endif // deduction guides

  /**
diff --git a/libstdc++-v3/testsuite/23_containers/map/cons/from_range.cc 
b/libstdc++-v3/testsuite/23_containers/map/cons/from_range.cc
new file mode 100644
index 00000000000..0744c92a73e
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/map/cons/from_range.cc
@@ -0,0 +1,155 @@
+// { dg-do run { target c++23 } }
+
+#include <algorithm>
+#include <map>
+#include <ranges>
+#include <span>
+#include <testsuite_allocator.h>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+#include <tuple>
+
+struct StateCmp {
+  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::map m(std::from_range, r);
+  static_assert(std::is_same_v<decltype(m), std::map<long, float>>);
+
+  StateCmp cmp;
+  std::map m2(std::from_range, r, cmp);
+  static_assert(std::is_same_v<decltype(m2), std::map<long, float, StateCmp>>);
+
+  using Alloc = __gnu_test::SimpleAllocator<std::pair<const long, float>>;
+  Alloc alloc;
+  std::map m3(std::from_range, r, alloc);
+  static_assert(std::is_same_v<decltype(m3), std::map<long, float, std::less<long>, 
Alloc>>);
+
+  std::map m4(std::from_range, r, cmp, alloc);
+  static_assert(std::is_same_v<decltype(m4), std::map<long, float, StateCmp, 
Alloc>>);
+
+  __gnu_test::test_input_range<std::pair<const long, const float>> r2(0, 0);
+  std::map m5(std::from_range, r2);
+  static_assert(std::is_same_v<decltype(m5), std::map<long, const float>>);
+
+  // LWG4223: deduces map<const long&, float&>
+  //__gnu_test::test_input_range<std::pair<const long&, float&>> r3(0, 0);
+  // std::map m6(std::from_range, r3);
+
+  // LWG4223: no deduction guide
+  // __gnu_test::test_input_range<std::tuple<long, float>> r4(0, 0);
+  // std::map m7(std::from_range, r4);
+}
+
+template<typename T, typename U>
+constexpr bool is_equal(std::less<T>, std::less<U>)
+{ return true; }
+
+constexpr bool is_equal(StateCmp lhs, StateCmp rhs)
+{ return lhs.state = rhs.state; }
+
+constexpr auto get0 = [](auto const& t) {
+  using std::get;
+  return get<0>(t);
+};
+
+template<typename Range, typename Alloc, typename Cmp>
+constexpr void
+do_test(Alloc alloc, Cmp cmp)
+{
+  // 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::map<K, V, Cmp, Alloc> const& l, std::span<T> r) {
+    if (l.size() != r.size())
+      return false;
+
+    std::vector<T> s(r.begin(), r.end());
+    std::ranges::sort(s, cmp, get0);
+    for (auto const& [vl, vr] : std::views::zip(l, s)) {
+      if (vl != vr)
+       return false;
+    }
+    return true;
+  };
+
+  std::map<K, V, Cmp, Alloc> m0(std::from_range, Range(a, a+0));
+  VERIFY( m0.empty() );
+  VERIFY( m0.get_allocator() == Alloc() );
+  VERIFY( is_equal(m0.key_comp(), Cmp()) );
+
+  std::map<K, V, Cmp, Alloc> m4(std::from_range, Range(a, a+4), cmp);
+  VERIFY( eq(m4, {a, 4}) );
+  VERIFY( m4.get_allocator() == Alloc() );
+  VERIFY( is_equal(m4.key_comp(), Cmp()) );
+
+  std::map<K, V, Cmp, Alloc> m9(std::from_range, Range(a, a+9), alloc);
+  VERIFY( eq(m9, {a, 9}) );
+  VERIFY( m9.get_allocator() == alloc );
+  VERIFY( is_equal(m9.key_comp(), cmp) );
+
+  std::map<K, V, Cmp, Alloc> mr(std::from_range, Range(a, a+14), cmp, alloc);
+  VERIFY( eq(mr, {a, 9}) );
+  VERIFY( mr.get_allocator() == alloc );
+  VERIFY( is_equal(mr.key_comp(), cmp) );
+}
+
+template<typename Range>
+void
+do_test_ac()
+{
+  do_test<Range>(std::allocator<std::pair<const int, double>>(), 
std::less<int>());
+  do_test<Range>(std::allocator<std::pair<const int, double>>(), StateCmp{17});
+  do_test<Range>(__gnu_test::uneq_allocator<std::pair<const int, double>>(42), 
std::less<int>());
+  do_test<Range>(__gnu_test::uneq_allocator<std::pair<const int, double>>(42), 
StateCmp{17});
+}
+
+struct MyPair {
+  long x;
+  long y;
+
+  constexpr operator std::pair<int const, double>() const
+  { return {x, y}; }
+
+  template<unsigned I>
+    requires (I < 2)
+  friend constexpr long get(MyPair p)
+  { return (I == 0) ? p.x : p.y; }
+
+  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_ac<test_forward_range<std::pair<int, double>>>();
+  do_test_ac<test_range_nocopy<std::pair<int, double>, 
input_iterator_wrapper_nocopy>>();
+  do_test_ac<test_forward_range<std::pair<short, float>>>();
+  do_test_ac<test_forward_range<std::tuple<int, double>>>();
+  do_test_ac<test_forward_range<MyPair>>();
+
+  return true;
+}
+
+int main()
+{
+  test_ranges();
+}
diff --git 
a/libstdc++-v3/testsuite/23_containers/map/modifiers/insert/insert_range.cc 
b/libstdc++-v3/testsuite/23_containers/map/modifiers/insert/insert_range.cc
new file mode 100644
index 00000000000..23ad0e8d427
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/map/modifiers/insert/insert_range.cc
@@ -0,0 +1,102 @@
+// { dg-do run { target c++23 } }
+
+#include <algorithm>
+#include <map>
+#include <ranges>
+#include <span>
+#include <testsuite_allocator.h>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+#include <tuple>
+
+struct Gt {
+  template<typename T, typename U>
+  bool operator()(T const& l, U const & r) const
+  { return l > r; }
+};
+
+constexpr auto get0 = [](auto const& t) {
+  using std::get;
+  return get<0>(t);
+};
+
+template<typename Range, typename K, typename V, typename Cmp>
+constexpr void
+do_test(Cmp cmp = Cmp())
+{
+  // 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::map<K, V, Cmp> const& l, std::span<T> r) {
+    if (l.size() != r.size())
+      return false;
+
+    std::vector<T> s(r.begin(), r.end());
+    std::ranges::sort(s, cmp, get0);
+    for (auto const& [vl, vr] : std::views::zip(l, s)) {
+      if (vl != vr)
+       return false;
+    }
+    return true;
+  };
+
+  std::map<K, V, Cmp> s;
+  VERIFY( s.empty() );
+
+  s.insert_range(Range(a, a+4));
+  VERIFY( eq(s, {a, 4}) );
+
+  s.insert_range(Range(a+4, a+9));
+  VERIFY( eq(s, {a, 9}) );
+
+  s.insert_range(Range(a, a+14));
+  VERIFY( eq(s, {a, 9}) );
+
+  s.insert_range(Range(a, a+14));
+  VERIFY( eq(s, {a, 9}) );
+}
+
+template<typename Range>
+void
+do_test_c()
+{
+  do_test<Range, int, double, std::less<int>>();
+  do_test<Range, int, double, Gt>();
+}
+
+struct MyPair {
+  long x;
+  long y;
+
+  constexpr operator std::pair<int const, double>() const
+  { return {x, y}; }
+
+  template<unsigned I>
+    requires (I < 2)
+  friend constexpr long get(MyPair p)
+  { return (I == 0) ? p.x : p.y; }
+
+  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_c<test_forward_range<std::pair<int, double>>>();
+  do_test_c<test_range_nocopy<std::pair<int, double>, 
input_iterator_wrapper_nocopy>>();
+  do_test_c<test_forward_range<std::pair<short, float>>>();
+  do_test_c<test_forward_range<std::tuple<int, double>>>();
+  do_test_c<test_forward_range<MyPair>>();
+
+  return true;
+}
+
+int main()
+{
+  test_ranges();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/multimap/cons/from_range.cc 
b/libstdc++-v3/testsuite/23_containers/multimap/cons/from_range.cc
new file mode 100644
index 00000000000..25d4a90a756
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/multimap/cons/from_range.cc
@@ -0,0 +1,155 @@
+// { dg-do run { target c++23 } }
+
+#include <algorithm>
+#include <map>
+#include <ranges>
+#include <span>
+#include <testsuite_allocator.h>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+#include <tuple>
+
+struct StateCmp {
+  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::multimap m(std::from_range, r);
+  static_assert(std::is_same_v<decltype(m), std::multimap<long, float>>);
+
+  StateCmp cmp;
+  std::multimap m2(std::from_range, r, cmp);
+  static_assert(std::is_same_v<decltype(m2), std::multimap<long, float, 
StateCmp>>);
+
+  using Alloc = __gnu_test::SimpleAllocator<std::pair<const long, float>>;
+  Alloc alloc;
+  std::multimap m3(std::from_range, r, alloc);
+  static_assert(std::is_same_v<decltype(m3), std::multimap<long, float, 
std::less<long>, Alloc>>);
+
+  std::multimap m4(std::from_range, r, cmp, alloc);
+  static_assert(std::is_same_v<decltype(m4), std::multimap<long, float, StateCmp, 
Alloc>>);
+
+  __gnu_test::test_input_range<std::pair<const long, const float>> r2(0, 0);
+  std::multimap m5(std::from_range, r2);
+  static_assert(std::is_same_v<decltype(m5), std::multimap<long, const 
float>>);
+
+  // LWG4223: deduces multimap<const long&, float&>
+  //__gnu_test::test_input_range<std::pair<const long&, float&>> r3(0, 0);
+  // std::multimap m6(std::from_range, r3);
+
+  // LWG4223: no deduction guide
+  // __gnu_test::test_input_range<std::tuple<long, float>> r4(0, 0);
+  // std::multimap m7(std::from_range, r4);
+}
+
+template<typename T, typename U>
+constexpr bool is_equal(std::less<T>, std::less<U>)
+{ return true; }
+
+constexpr bool is_equal(StateCmp lhs, StateCmp rhs)
+{ return lhs.state = rhs.state; }
+
+constexpr auto get0 = [](auto const& t) {
+  using std::get;
+  return get<0>(t);
+};
+
+template<typename Range, typename Alloc, typename Cmp>
+constexpr void
+do_test(Alloc alloc, Cmp cmp)
+{
+  // The multimap's value_type, key_type and multimapped_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::multimap<K, V, Cmp, Alloc> const& l, std::span<T> r) {
+    if (l.size() != r.size())
+      return false;
+
+    std::vector<T> s(r.begin(), r.end());
+    std::ranges::stable_sort(s, cmp, get0);
+    for (auto const& [vl, vr] : std::views::zip(l, s)) {
+      if (vl != vr)
+       return false;
+    }
+    return true;
+  };
+
+  std::multimap<K, V, Cmp, Alloc> m0(std::from_range, Range(a, a+0));
+  VERIFY( m0.empty() );
+  VERIFY( m0.get_allocator() == Alloc() );
+  VERIFY( is_equal(m0.key_comp(), Cmp()) );
+
+  std::multimap<K, V, Cmp, Alloc> m4(std::from_range, Range(a, a+4), cmp);
+  VERIFY( eq(m4, {a, 4}) );
+  VERIFY( m4.get_allocator() == Alloc() );
+  VERIFY( is_equal(m4.key_comp(), Cmp()) );
+
+  std::multimap<K, V, Cmp, Alloc> m9(std::from_range, Range(a, a+9), alloc);
+  VERIFY( eq(m9, {a, 9}) );
+  VERIFY( m9.get_allocator() == alloc );
+  VERIFY( is_equal(m9.key_comp(), cmp) );
+
+  std::multimap<K, V, Cmp, Alloc> mr(std::from_range, Range(a, a+14), cmp, 
alloc);
+  VERIFY( eq(mr, {a, 14}) );
+  VERIFY( mr.get_allocator() == alloc );
+  VERIFY( is_equal(mr.key_comp(), cmp) );
+}
+
+template<typename Range>
+void
+do_test_ac()
+{
+  do_test<Range>(std::allocator<std::pair<const int, double>>(), 
std::less<int>());
+  do_test<Range>(std::allocator<std::pair<const int, double>>(), StateCmp{17});
+  do_test<Range>(__gnu_test::uneq_allocator<std::pair<const int, double>>(42), 
std::less<int>());
+  do_test<Range>(__gnu_test::uneq_allocator<std::pair<const int, double>>(42), 
StateCmp{17});
+}
+
+struct MyPair {
+  long x;
+  long y;
+
+  constexpr operator std::pair<int const, double>() const
+  { return {x, y}; }
+
+  template<unsigned I>
+    requires (I < 2)
+  friend constexpr long get(MyPair p)
+  { return (I == 0) ? p.x : p.y; }
+
+  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_ac<test_forward_range<std::pair<int, double>>>();
+  do_test_ac<test_range_nocopy<std::pair<int, double>, 
input_iterator_wrapper_nocopy>>();
+  do_test_ac<test_forward_range<std::pair<short, float>>>();
+  do_test_ac<test_forward_range<std::tuple<int, double>>>();
+  do_test_ac<test_forward_range<MyPair>>();
+
+  return true;
+}
+
+int main()
+{
+  test_ranges();
+}
diff --git 
a/libstdc++-v3/testsuite/23_containers/multimap/modifiers/insert/insert_range.cc
 
b/libstdc++-v3/testsuite/23_containers/multimap/modifiers/insert/insert_range.cc
new file mode 100644
index 00000000000..e8db38b0a9b
--- /dev/null
+++ 
b/libstdc++-v3/testsuite/23_containers/multimap/modifiers/insert/insert_range.cc
@@ -0,0 +1,99 @@
+// { dg-do run { target c++23 } }
+
+#include <algorithm>
+#include <map>
+#include <ranges>
+#include <span>
+#include <testsuite_allocator.h>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+#include <tuple>
+
+struct Gt {
+  template<typename T, typename U>
+  bool operator()(T const& l, U const & r) const
+  { return l > r; }
+};
+
+constexpr auto get0 = [](auto const& t) {
+  using std::get;
+  return get<0>(t);
+};
+
+template<typename Range, typename K, typename V, typename Cmp>
+constexpr void
+do_test(Cmp cmp = Cmp())
+{
+  // 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::multimap<K, V, Cmp> const& l, std::span<T> r) {
+    if (l.size() != r.size())
+      return false;
+
+    std::vector<T> s(r.begin(), r.end());
+    std::ranges::stable_sort(s, cmp, get0);
+    for (auto const& [vl, vr] : std::views::zip(l, s)) {
+      if (vl != vr)
+       return false;
+    }
+    return true;
+  };
+
+  std::multimap<K, V, Cmp> s;
+  VERIFY( s.empty() );
+
+  s.insert_range(Range(a, a+4));
+  VERIFY( eq(s, {a, 4}) );
+
+  s.insert_range(Range(a+4, a+9));
+  VERIFY( eq(s, {a, 9}) );
+
+  s.insert_range(Range(a+9, a+14));
+  VERIFY( eq(s, {a, 14}) );
+}
+
+template<typename Range>
+void
+do_test_c()
+{
+  do_test<Range, int, double, std::less<int>>();
+  do_test<Range, int, double, Gt>();
+}
+
+struct MyPair {
+  long x;
+  long y;
+
+  constexpr operator std::pair<int const, double>() const
+  { return {x, y}; }
+
+  template<unsigned I>
+    requires (I < 2)
+  friend constexpr long get(MyPair p)
+  { return (I == 0) ? p.x : p.y; }
+
+  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_c<test_forward_range<std::pair<int, double>>>();
+  do_test_c<test_range_nocopy<std::pair<int, double>, 
input_iterator_wrapper_nocopy>>();
+  do_test_c<test_forward_range<std::pair<short, float>>>();
+  do_test_c<test_forward_range<std::tuple<int, double>>>();
+  do_test_c<test_forward_range<MyPair>>();
+
+  return true;
+}
+
+int main()
+{
+  test_ranges();
+}
--
2.48.1


Reply via email to