On 18/03/25 12:48 +0100, Tomasz Kaminski wrote:
On Tue, Mar 18, 2025 at 12:24 PM Jonathan Wakely <jwak...@redhat.com> wrote:

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?!

That ship sailed with *iter-to-alloc-type in *C++17.

Ah yes :-\


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.

I prefer it being an alias to an allocated type, that would make it easier
to go with the rebind approach.

Agreed.


> }
> /// @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?

Maps have perfect forwarding insert(Pair&&) overload, that I am calling
here,
if *__first is not exactly value_type&& or value_type const&.
So I do not see where temporary could be created.

Oh right, I looked at that overload and for some reason I thought it
was constrained with __is_pair<_Pair> ... but it's not. The constraint
is is_constructible<value_type, _Pair> which is fine. Sorry!

I want this overload to be called, because it has special magic for
decomposing pairs that I want to use.

Right (emplace has that too, but less flexible, as it only decomposes
when it's exactly the same pair specialization as value_type).


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.

I do not see how this falls out from the implementation.

Agreed.


>+      }
>+#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/

So it's just this change that is needed then.

OK for trunk with that fixed, thanks!

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