From: Nathan Myers Date: Fri, 5 Jun 2026 03:42:11 +0000 (-0400) Subject: libstdc++: make trees hetero equal_range O(lg n) [PR118851] X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=6fc5746a5e809e4f22f343eb51bb963cd151ee7e;p=thirdparty%2Fgcc.git libstdc++: make trees hetero equal_range O(lg n) [PR118851] As shipped, the heterogeneous-key members equal_range of rbtree containers map, multimap, set, and multiset walk the entire range calling the predicate, in violation of the requirement for logarithmic complexity. This patch revises them to match the behavior of the non-heterogeneous multimap and multiset members, and provides tests to verify it. libstdc++-v3/Changelog: PR libstdc++/118851 * include/bits/stl_tree.h (_M_equal_range_tr): Rewrite to match non-heterogeneous equal_range implementation. * testsuite/23_containers/map/operations/hetero/equal_range.cc: New test. * testsuite/23_containers/multimap/operations/hetero/equal_range.cc: Same. * testsuite/23_containers/multiset/operations/hetero/equal_range.cc: Same. * testsuite/23_containers/set/operations/hetero/equal_range.cc: Same. --- diff --git a/libstdc++-v3/include/bits/stl_tree.h b/libstdc++-v3/include/bits/stl_tree.h index a7781b72114..313ee816002 100644 --- a/libstdc++-v3/include/bits/stl_tree.h +++ b/libstdc++-v3/include/bits/stl_tree.h @@ -2026,12 +2026,29 @@ namespace __rb_tree pair _M_equal_range_tr(const _Kt& __k) const { - const_iterator __low(_M_lower_bound_tr(__k)); - auto __high = __low; - auto& __cmp = _M_impl._M_key_compare; - while (__high != end() && !__cmp(__k, _S_key(__high._M_node))) - ++__high; - return { __low, __high }; + auto __x = _M_begin(); + auto __y = _M_end(); + while (__x) + { + if (_M_key_compare(_S_key(__x), __k)) + __x = _S_right(__x); + else if (_M_key_compare(__k, _S_key(__x))) + { + __y = __x; + __x = _S_left(__x); + } + else + { + auto __xu(__x); + auto __yu(__y); + __y = __x; + __x = _S_left(__x); + __xu = _S_right(__xu); + return { const_iterator(_M_lower_bound_tr(__x, __y, __k)), + const_iterator(_M_upper_bound_tr(__xu, __yu, __k)) }; + } + } + return { const_iterator(__y), const_iterator(__y) }; } #endif // __glibcxx_generic_associative_lookup diff --git a/libstdc++-v3/testsuite/23_containers/map/operations/hetero/equal_range.cc b/libstdc++-v3/testsuite/23_containers/map/operations/hetero/equal_range.cc new file mode 100644 index 00000000000..3ed6ff62ba8 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/map/operations/hetero/equal_range.cc @@ -0,0 +1,90 @@ +// { dg-do run { target c++14 } } + +#include +#include +#include +#include +#include + +struct Z; + +struct X { + std::string s; + X(std::string str) : s(str) {} + X(Z&&); + X(const Z&); + friend bool operator<(X const& a, X const& b) { return a.s < b.s; } + friend bool operator==(X const& a, X const& b) { return a.s == b.s; } +}; + +using cmp = std::less; + +struct Z { + std::string s; + mutable int compares = 0; + + Z() = default; + Z(Z&& z) : s(std::move(z.s)) { z.s.clear(); } + Z(const Z& z) = default; + Z& operator=(Z&& z) { s = std::move(z.s); z.s.clear(); return *this; } + Z& operator=(const Z& z) = default; + Z(std::string s) : s(s) {} + Z(int n) : s(std::string(n, 'a')) {} + friend bool operator<(Z const& a, Z const& b) { return a.s < b.s; } + friend bool operator<(X const& a, Z const& b) + { return ++b.compares, a.s.substr(0, b.s.size()) < b.s; } + friend bool operator<(Z const& a, X const& b) + { return ++a.compares, a.s < b.s.substr(0, a.s.size()); } +}; + +X::X(Z&& z) : s(std::move(z.s)) { z.s.clear(); } +X::X(const Z& z) : s(z.s) {} + +// A heterogeneous key type like Z here that compares equal +// if it matches just the first part of the key is allowed. + +template +T populate(T a) +{ + const std::string vs[] = { "dec", "ded", "dee", "def", "deg", "deh", "dei" }; + for (auto const& v : vs) + a[v] = v+v; + return a; +} + +void test_equal_range() +{ + std::map amap{cmp{}}; + amap.insert({{X{"abc"}, {"xyz"}}, {X{"def"}, {"uvw"}}, {X{"ghi"}, {"rst"}}}); + + { + auto a = populate(amap); + VERIFY(a.size() == 9); + Z z{"de"}; + auto it = a.equal_range(z); + VERIFY(it.first != a.end()); + VERIFY(it.second != a.end()); + VERIFY(it.first->first == std::string("dec")); + VERIFY(it.second->first == std::string("ghi")); + + VERIFY(z.compares == 7); + } + { + auto a = populate(amap); + VERIFY(a.size() == 9); + Z z{"de"}; + auto const& ca = a; + auto it = ca.equal_range(z); + VERIFY(it.first != ca.end()); + VERIFY(it.second != ca.end()); + VERIFY(it.first->first == std::string("dec")); + VERIFY(it.second->first == std::string("ghi")); + + VERIFY(z.compares == 7); + } +} + +int main() +{ + test_equal_range(); +} diff --git a/libstdc++-v3/testsuite/23_containers/multimap/operations/hetero/equal_range.cc b/libstdc++-v3/testsuite/23_containers/multimap/operations/hetero/equal_range.cc new file mode 100644 index 00000000000..490407c244d --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/multimap/operations/hetero/equal_range.cc @@ -0,0 +1,90 @@ +// { dg-do run { target c++14 } } + +#include +#include +#include +#include +#include + +struct Z; + +struct X { + std::string s; + X(std::string str) : s(str) {} + X(Z&&); + X(const Z&); + friend bool operator<(X const& a, X const& b) { return a.s < b.s; } + friend bool operator==(X const& a, X const& b) { return a.s == b.s; } +}; + +using cmp = std::less; + +struct Z { + std::string s; + mutable int compares = 0; + + Z() = default; + Z(Z&& z) : s(std::move(z.s)) { z.s.clear(); } + Z(const Z& z) = default; + Z& operator=(Z&& z) { s = std::move(z.s); z.s.clear(); return *this; } + Z& operator=(const Z& z) = default; + Z(std::string sv) : s(sv) {} + Z(int n) : s(std::string(n, 'a')) {} + friend bool operator<(Z const& a, Z const& b) { return a.s < b.s; } + friend bool operator<(X const& a, Z const& b) + { return ++b.compares, a.s.substr(0, b.s.size()) < b.s; } + friend bool operator<(Z const& a, X const& b) + { return ++a.compares, a.s < b.s.substr(0, a.s.size()); } +}; + +X::X(Z&& z) : s(std::move(z.s)) { z.s.clear(); } +X::X(const Z& z) : s(z.s) {} + +// A heterogeneous key type like Z here that compares equal +// if it matches just the first part of the key is allowed. + +template +T populate(T a) +{ + const std::string vs[] = { "dec", "ded", "dee", "def", "deg", "deh", "dei" }; + for (auto const& v : vs) + a.insert({X{v}, {v+v}}); + return a; +} + +void test_equal_range() +{ + std::multimap amap{cmp{}}; + amap.insert({{X{"abc"}, {"xyz"}}, {X{"def"}, {"uvw"}}, {X{"ghi"}, {"rst"}}}); + + { + auto a = populate(amap); + VERIFY(a.size() == 10); + Z z{"de"}; + auto it = a.equal_range(z); + VERIFY(it.first != a.end()); + VERIFY(it.second != a.end()); + VERIFY(it.first->first == std::string("dec")); + VERIFY(it.second->first == std::string("ghi")); + + VERIFY(z.compares == 7); + } + { + auto a = populate(amap); + VERIFY(a.size() == 10); + Z z{"de"}; + auto const& ca = a; + auto it = ca.equal_range(z); + VERIFY(it.first != ca.end()); + VERIFY(it.second != ca.end()); + VERIFY(it.first->first == std::string("dec")); + VERIFY(it.second->first == std::string("ghi")); + + VERIFY(z.compares == 7); + } +} + +int main() +{ + test_equal_range(); +} diff --git a/libstdc++-v3/testsuite/23_containers/multiset/operations/hetero/equal_range.cc b/libstdc++-v3/testsuite/23_containers/multiset/operations/hetero/equal_range.cc new file mode 100644 index 00000000000..a9a479a78cd --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/multiset/operations/hetero/equal_range.cc @@ -0,0 +1,90 @@ +// { dg-do run { target c++14 } } + +#include +#include +#include +#include +#include + +struct Z; + +struct X { + std::string s; + X(std::string str) : s(str) {} + X(Z&&); + X(const Z&); + friend bool operator<(X const& a, X const& b) { return a.s < b.s; } + friend bool operator==(X const& a, X const& b) { return a.s == b.s; } +}; + +using cmp = std::less; + +struct Z { + std::string s; + mutable int compares = 0; + + Z() = default; + Z(Z&& z) : s(std::move(z.s)) { z.s.clear(); } + Z(const Z& z) = default; + Z& operator=(Z&& z) { s = std::move(z.s); z.s.clear(); return *this; } + Z& operator=(const Z& z) = default; + Z(std::string sv) : s(sv) {} + Z(int n) : s(std::string(n, 'a')) {} + friend bool operator<(Z const& a, Z const& b) { return a.s < b.s; } + friend bool operator<(X const& a, Z const& b) + { return ++b.compares, a.s.substr(0, b.s.size()) < b.s; } + friend bool operator<(Z const& a, X const& b) + { return ++a.compares, a.s < b.s.substr(0, a.s.size()); } +}; + +X::X(Z&& z) : s(std::move(z.s)) { z.s.clear(); } +X::X(const Z& z) : s(z.s) {} + +// A heterogeneous key type like Z here that compares equal +// if it matches just the first part of the key is allowed. + +template +T populate(T a) +{ + const std::string vs[] = { "dec", "ded", "dee", "def", "deg", "deh", "dei" }; + for (auto const& v : vs) + a.insert(X{v}); + return a; +} + +void test_equal_range() +{ + std::multiset aset{cmp{}}; + aset.insert({X{"abc"}, X{"def"}, X{"ghi"}}); + + { + auto a = populate(aset); + VERIFY(a.size() == 10); + Z z{"de"}; + auto it = a.equal_range(z); + VERIFY(it.first != a.end()); + VERIFY(it.second != a.end()); + VERIFY(*it.first == std::string("dec")); + VERIFY(*it.second == std::string("ghi")); + + VERIFY(z.compares == 7); + } + { + auto a = populate(aset); + VERIFY(a.size() == 10); + Z z{"de"}; + auto const& ca = a; + auto it = ca.equal_range(z); + VERIFY(it.first != ca.end()); + VERIFY(it.second != ca.end()); + VERIFY(*it.first == std::string("dec")); + VERIFY(*it.second == std::string("ghi")); + + VERIFY(z.compares == 7); + } +} + +int main() +{ + test_equal_range(); +} diff --git a/libstdc++-v3/testsuite/23_containers/set/operations/hetero/equal_range.cc b/libstdc++-v3/testsuite/23_containers/set/operations/hetero/equal_range.cc new file mode 100644 index 00000000000..1c9e7a33518 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/set/operations/hetero/equal_range.cc @@ -0,0 +1,90 @@ +// { dg-do run { target c++14 } } + +#include +#include +#include +#include +#include + +struct Z; + +struct X { + std::string s; + X(std::string str) : s(str) {} + X(Z&&); + X(const Z&); + friend bool operator<(X const& a, X const& b) { return a.s < b.s; } + friend bool operator==(X const& a, X const& b) { return a.s == b.s; } +}; + +using cmp = std::less; + +struct Z { + std::string s; + mutable int compares = 0; + + Z() = default; + Z(Z&& z) : s(std::move(z.s)) { z.s.clear(); } + Z(const Z& z) = default; + Z& operator=(Z&& z) { s = std::move(z.s); z.s.clear(); return *this; } + Z& operator=(const Z& z) = default; + Z(std::string sv) : s(sv) {} + Z(int n) : s(std::string(n, 'a')) {} + friend bool operator<(Z const& a, Z const& b) { return a.s < b.s; } + friend bool operator<(X const& a, Z const& b) + { return ++b.compares, a.s.substr(0, b.s.size()) < b.s; } + friend bool operator<(Z const& a, X const& b) + { return ++a.compares, a.s < b.s.substr(0, a.s.size()); } +}; + +X::X(Z&& z) : s(std::move(z.s)) { z.s.clear(); } +X::X(const Z& z) : s(z.s) {} + +// A heterogeneous key type like Z here that compares equal +// if it matches just the first part of the key is allowed. + +template +T populate(T a) +{ + const std::string vs[] = { "dec", "ded", "dee", "def", "deg", "deh", "dei" }; + for (auto const& v : vs) + a.insert(X{v}); + return a; +} + +void test_equal_range() +{ + std::set aset{cmp{}}; + aset.insert({X{"abc"}, X{"def"}, X{"ghi"}}); + + { + auto a = populate(aset); + VERIFY(a.size() == 9); + Z z{"de"}; + auto it = a.equal_range(z); + VERIFY(it.first != a.end()); + VERIFY(it.second != a.end()); + VERIFY(*it.first == std::string("dec")); + VERIFY(*it.second == std::string("ghi")); + + VERIFY(z.compares == 7); + } + { + auto a = populate(aset); + VERIFY(a.size() == 9); + Z z{"de"}; + auto const& ca = a; + auto it = ca.equal_range(z); + VERIFY(it.first != ca.end()); + VERIFY(it.second != ca.end()); + VERIFY(*it.first == std::string("dec")); + VERIFY(*it.second == std::string("ghi")); + + VERIFY(z.compares == 7); + } +} + +int main() +{ + test_equal_range(); +}