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
>