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. > > 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. > > > } > > /// @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. I want this overload to be called, because it has special magic for decomposing pairs that I want to use. > > 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. > > >+ } > >+#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 > > > >