From 6f6c88be2b6063f762613f14c0d6c7b872e412a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 13:37:54 +0100 Subject: [PATCH 01/25] Add _NODISCARD to flat_map and flat_set. --- stl/inc/flat_map | 59 ++++++++++++++++++++++++------------------------ stl/inc/flat_set | 2 +- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index 77d6736e8db..36b407ab1a5 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -36,7 +36,8 @@ template struct _Flat_map_value_compare_provider { struct value_compare { public: - bool operator()(pair _Left, pair _Right) const { + _NODISCARD bool operator()( + pair _Left, pair _Right) const { return _Key_comparator(_Left.first, _Right.first); } @@ -97,7 +98,7 @@ struct _Pairing_iterator_provider { public: explicit _Arrow_proxy(const reference& _Rx) noexcept : _Ref{_Rx} {} - const reference* operator->() const noexcept { + _NODISCARD const reference* operator->() const noexcept { return _STD addressof(_Ref); } @@ -108,16 +109,16 @@ struct _Pairing_iterator_provider { public: using pointer = _Arrow_proxy; - reference operator*() const { + _NODISCARD reference operator*() const { return reference{*_Key_it, *_Mapped_it}; } - friend pair, iter_rvalue_reference_t<_MappedIter>> iter_move( + _NODISCARD friend pair, iter_rvalue_reference_t<_MappedIter>> iter_move( const _Iterator& _It) { return {_RANGES iter_move(_It._Key_it), _RANGES iter_move(_It._Mapped_it)}; } - pointer operator->() const { + _NODISCARD pointer operator->() const { return pointer{**this}; } @@ -133,11 +134,11 @@ struct _Pairing_iterator_provider { return _Old; } - bool operator==(const _Iterator& _Right) const { + _NODISCARD bool operator==(const _Iterator& _Right) const { return _Key_it == _Right._Key_it; } - auto operator<=>(const _Iterator& _Right) const { + _NODISCARD auto operator<=>(const _Iterator& _Right) const { return _Synth_three_way{}(_Key_it, _Right._Key_it); } @@ -165,41 +166,41 @@ struct _Pairing_iterator_provider { return *this; } - _Iterator operator+(const difference_type _Off) const { + _NODISCARD _Iterator operator+(const difference_type _Off) const { auto _Old = *this; _Old += _Off; return _Old; } - _Iterator operator-(const difference_type _Off) const { + _NODISCARD _Iterator operator-(const difference_type _Off) const { auto _Old = *this; _Old -= _Off; return _Old; } - reference operator[](const difference_type _Off) const { + _NODISCARD reference operator[](const difference_type _Off) const { return *(*this + _Off); } - difference_type operator-(const _Iterator& _Right) const { + _NODISCARD difference_type operator-(const _Iterator& _Right) const { return _Key_it - _Right._Key_it; } - friend _Iterator operator+(const difference_type _Off, const _Iterator& _Right) { + _NODISCARD friend _Iterator operator+(const difference_type _Off, const _Iterator& _Right) { return _Right + _Off; } - operator _Const_iterator() const + _NODISCARD operator _Const_iterator() const requires (!is_same_v<_MappedIter, _MappedConvIter>) { return _Const_iterator{_Key_it, _Mapped_it}; } - const _KeyIter& _Key_iterator() const noexcept { + _NODISCARD const _KeyIter& _Key_iterator() const noexcept { return _Key_it; } - const _MappedIter& _Mapped_iterator() const noexcept { + _NODISCARD const _MappedIter& _Mapped_iterator() const noexcept { return _Mapped_it; } @@ -694,13 +695,13 @@ public: return _Erase_key(_STD forward<_OtherKey>(_Key_val)); } - containers extract() && { + _NODISCARD containers extract() && { _Clear_guard _Guard{this}; return _STD move(_Data); } template - containers _Extract_using_allocator(const _Allocator& _Alloc) { + _NODISCARD containers _Extract_using_allocator(const _Allocator& _Alloc) { _Clear_guard _Guard{this}; return containers{.keys = _STD make_obj_using_allocator(_Alloc, _STD move(_Data.keys)), .values = _STD make_obj_using_allocator(_Alloc, _STD move(_Data.values))}; @@ -714,19 +715,19 @@ public: } // observers - key_compare key_comp() const { + _NODISCARD key_compare key_comp() const { return _Key_compare; } - value_compare value_comp() const { + _NODISCARD value_compare value_comp() const { return value_compare{_Key_compare}; } - const key_container_type& keys() const noexcept { + _NODISCARD const key_container_type& keys() const noexcept { return _Data.keys; } - const mapped_container_type& values() const noexcept { + _NODISCARD const mapped_container_type& values() const noexcept { return _Data.values; } @@ -862,7 +863,7 @@ protected: _MSVC_NO_UNIQUE_ADDRESS key_compare _Key_compare; template - iterator _Emplace_hint(const const_iterator _Position, _OtherKey&& _Key_val, _MappedArgTypes&&... _Args) { + _NODISCARD iterator _Emplace_hint(const const_iterator _Position, _OtherKey&& _Key_val, _MappedArgTypes&&... _Args) { _STL_INTERNAL_STATIC_ASSERT(is_constructible_v); _STL_INTERNAL_STATIC_ASSERT(is_same_v, key_type> || (is_constructible_v && _Transparent) ); @@ -945,7 +946,7 @@ protected: } template - bool _Key_equal(_KeyTy1&& _Left, _KeyTy2&& _Right) const { + _NODISCARD bool _Key_equal(_KeyTy1&& _Left, _KeyTy2&& _Right) const { _STL_INTERNAL_STATIC_ASSERT( (is_same_v, key_type> && is_same_v, key_type>) || (is_constructible_v && is_constructible_v @@ -964,7 +965,7 @@ private: flat_multimap<_KTy, _MTy, _Comp, _KeyCont, _MappedCont>&, _Pred); template - size_type _Erase_if(_Predicate _Pred) { + _NODISCARD size_type _Erase_if(_Predicate _Pred) { auto _View = _View_to_mutate(); auto _Mut_first = _View.begin(); auto _Mut_last = _View.end(); @@ -980,7 +981,7 @@ private: return _Old_size - size(); } - auto _View_to_mutate() { + _NODISCARD auto _View_to_mutate() { using _Mutating_iterator = _Pairing_iterator_provider::_Iterator; return _RANGES subrange<_Mutating_iterator>{_Mutating_iterator{_Data.keys.begin(), _Data.values.begin()}, @@ -1049,7 +1050,7 @@ private: } template - size_type _Erase_key(_KeyTy&& _Key_val) { + _NODISCARD size_type _Erase_key(_KeyTy&& _Key_val) { const auto _Equal_pos = equal_range(_STD forward<_KeyTy>(_Key_val)); const auto _Count = static_cast(_Equal_pos.second - _Equal_pos.first); erase(_Equal_pos.first, _Equal_pos.second); @@ -1378,7 +1379,7 @@ private: } template - pair _Try_emplace(_KeyTy&& _Key_val, _MappedArgTypes&&... _Mapped_args) { + _NODISCARD pair _Try_emplace(_KeyTy&& _Key_val, _MappedArgTypes&&... _Mapped_args) { auto _Key_it = _STD lower_bound(_Data.keys.begin(), _Data.keys.end(), _Key_val, _Key_compare); if (_Key_it != _Data.keys.end() && !_Key_compare(_STD forward<_KeyTy>(_Key_val), *_Key_it)) { // Already exists @@ -1396,7 +1397,7 @@ private: } template - pair _Insert_or_assign(_KeyTy&& _Key_val, _MappedTy&& _Mapped_val) { + _NODISCARD pair _Insert_or_assign(_KeyTy&& _Key_val, _MappedTy&& _Mapped_val) { auto _Res = _Try_emplace(_STD forward<_KeyTy>(_Key_val), _STD forward<_MappedTy>(_Mapped_val)); if (!_Res.second) { // Already exists _Res.first->second = _STD forward<_MappedTy>(_Mapped_val); @@ -1555,7 +1556,7 @@ private: using _Mybase::_Key_equal; template - iterator _Emplace_key_mapped(_KeyTy&& _Key_val, _MappedTy&& _Mapped_val) { + _NODISCARD iterator _Emplace_key_mapped(_KeyTy&& _Key_val, _MappedTy&& _Mapped_val) { const auto _Key_it = _STD upper_bound(_Data.keys.begin(), _Data.keys.end(), _Key_val, _Key_compare); const auto _Index = _Key_it - _Data.keys.begin(); diff --git a/stl/inc/flat_set b/stl/inc/flat_set index eb9017af000..d05e63e8b68 100644 --- a/stl/inc/flat_set +++ b/stl/inc/flat_set @@ -648,7 +648,7 @@ private: friend typename _Container2::size_type erase_if(flat_multiset<_Kty2, _Keylt2, _Container2>&, _Pred2); template - size_type _Erase_if(_Pred _Predicate) { + _NODISCARD size_type _Erase_if(_Pred _Predicate) { // Maintain invariants when an exception is thrown (N4950 [flat.set.erasure]/5, [flat.multiset.erasure]/5) _Clear_guard _Guard{this}; const auto _Erased_count = _STD _Erase_remove_if(_Mycont, _Predicate); From bdd8da947b1b21e68bc7e73efe5084b778b6b47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 13:49:57 +0100 Subject: [PATCH 02/25] Don't query the nothrow_move_constructibility of flat_map::key_compare when it is only copied. --- stl/inc/flat_map | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index 36b407ab1a5..e3418e39e94 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -496,8 +496,7 @@ public: // Move constructors _Flat_map_base(_Flat_map_base&& _Other) - noexcept(is_nothrow_copy_constructible_v && is_nothrow_move_constructible_v - && is_nothrow_move_constructible_v + noexcept(is_nothrow_copy_constructible_v && is_nothrow_move_constructible_v && is_nothrow_move_constructible_v) : _Data(_STD move(_Other).extract()), _Key_compare(_Other._Key_compare) // intentionally copy comparator, see LWG-2227 @@ -518,9 +517,9 @@ public: return *this; } - _Flat_map_base& operator=(_Flat_map_base&& _Other) noexcept( - is_nothrow_copy_assignable_v && is_nothrow_move_assignable_v - && is_nothrow_move_assignable_v && is_nothrow_move_assignable_v) { + _Flat_map_base& operator=(_Flat_map_base&& _Other) + noexcept(is_nothrow_copy_assignable_v && is_nothrow_move_assignable_v + && is_nothrow_move_assignable_v) { _Clear_guard _Guard{this}; _Data = _STD move(_Other).extract(); _Key_compare = _Other._Key_compare; // intentionally copy comparator, see LWG-2227 From 6ca26ba569ca663f2944e140f6ec3f91c27d4dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 13:52:16 +0100 Subject: [PATCH 03/25] flat_map::_Extract_using_allocator should be rvalue-ref qualified. --- stl/inc/flat_map | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index e3418e39e94..a4aa14c141c 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -504,7 +504,7 @@ public: template <_Usable_allocator_for _Allocator> _Flat_map_base(_Flat_map_base&& _Other, const _Allocator& _Alloc) - : _Data{_Other._Extract_using_allocator(_Alloc)}, + : _Data{_STD move(_Other)._Extract_using_allocator(_Alloc)}, _Key_compare(_Other._Key_compare) // intentionally copy comparator, see LWG-2227 {} @@ -700,7 +700,7 @@ public: } template - _NODISCARD containers _Extract_using_allocator(const _Allocator& _Alloc) { + _NODISCARD containers _Extract_using_allocator(const _Allocator& _Alloc) && { _Clear_guard _Guard{this}; return containers{.keys = _STD make_obj_using_allocator(_Alloc, _STD move(_Data.keys)), .values = _STD make_obj_using_allocator(_Alloc, _STD move(_Data.values))}; From c42901c0e6dacedcf0d2b26d860e77f0c2e2aa62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 14:05:59 +0100 Subject: [PATCH 04/25] Make flat_map::extract consistent with flat_set::extract. --- stl/inc/flat_map | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index a4aa14c141c..eef72dff3a0 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -694,8 +694,11 @@ public: return _Erase_key(_STD forward<_OtherKey>(_Key_val)); } - _NODISCARD containers extract() && { - _Clear_guard _Guard{this}; + _NODISCARD containers extract() && noexcept( + is_nothrow_move_constructible_v + && is_nothrow_move_constructible_v) /* strengthened */ { + // always clears the container (N4950 [flat.map.modifiers]/33 and [flat.multimap.overview]/4 + _Clear_guard _Always_clear{this}; return _STD move(_Data); } From 85d1c99e62506bf7142b50b65e53ed75b533e245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 14:11:52 +0100 Subject: [PATCH 05/25] flat_set::_Erase should not pessimize performance for different _Ty and _Kty. --- stl/inc/flat_set | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/flat_set b/stl/inc/flat_set index d05e63e8b68..8e20f495828 100644 --- a/stl/inc/flat_set +++ b/stl/inc/flat_set @@ -625,7 +625,7 @@ private: _NODISCARD size_type _Erase(const _Ty& _Val) { _STL_INTERNAL_STATIC_ASSERT(_Transparent || is_same_v<_Ty, _Kty>); - if constexpr (_IsUnique && is_same_v<_Ty, _Kty>) { + if constexpr (_IsUnique) { const const_iterator _Where = lower_bound(_Val); if (_Where != cend() && !_Compare(_Val, *_Where)) { _Mycont.erase(_Where); From d7085de53283de952e47b1df7d732aaef9cc9a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 15:15:38 +0100 Subject: [PATCH 06/25] Some libcxx tests have to be FAILed due to throwing move constructor of comparator. --- tests/libcxx/expected_results.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index aaa379c59b8..606259690c5 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -268,6 +268,10 @@ std/strings/c.strings/cuchar.compile.pass.cpp FAIL # P0429R9 +# static_assert(!std::is_nothrow_move_constructible_v) with C = std::flat_(multi)map +std/containers/container.adaptors/flat.multimap/flat.multimap.cons/move_noexcept.pass.cpp FAIL +std/containers/container.adaptors/flat.map/flat.map.cons/move_noexcept.pass.cpp FAIL + # FIXME! warning C4242: 'initializing': conversion from '_Ty' to '_Ty2', possible loss of data std/containers/container.adaptors/flat.map/flat.map.cons/copy_assign.pass.cpp:0 FAIL std/containers/container.adaptors/flat.map/flat.map.cons/copy_assign.pass.cpp:1 FAIL From 9b185b8e7c6372672aa226a2604c93ee9eabb904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 15:45:12 +0100 Subject: [PATCH 07/25] Remove nested _Clear_guards form flat_map --- stl/inc/flat_map | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index eef72dff3a0..a6829d8b868 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -359,30 +359,21 @@ public: _Flat_map_base( key_container_type _Key_cont, mapped_container_type _Mapped_cont, const key_compare& _Comp = key_compare()) : _Data{.keys = _STD move(_Key_cont), .values = _STD move(_Mapped_cont)}, _Key_compare(_Comp) { - _Sort(); - if constexpr (_IsUnique) { - _Dedup(); - } + _Sort_and_dedup_if_unique(); } template <_Usable_allocator_for _Allocator> _Flat_map_base( const key_container_type& _Key_cont, const mapped_container_type& _Mapped_cont, const _Allocator& _Alloc) : _Flat_map_base(_Sorted_t{}, _Key_cont, _Mapped_cont, _Alloc) { - _Sort(); - if constexpr (_IsUnique) { - _Dedup(); - } + _Sort_and_dedup_if_unique(); } template <_Usable_allocator_for _Allocator> _Flat_map_base(const key_container_type& _Key_cont, const mapped_container_type& _Mapped_cont, const key_compare& _Comp, const _Allocator& _Alloc) : _Flat_map_base(_Sorted_t{}, _Key_cont, _Mapped_cont, _Comp, _Alloc) { - _Sort(); - if constexpr (_IsUnique) { - _Dedup(); - } + _Sort_and_dedup_if_unique(); } _Flat_map_base(_Sorted_t, key_container_type _Key_cont, mapped_container_type _Mapped_cont, @@ -990,14 +981,16 @@ private: _Mutating_iterator{_Data.keys.end(), _Data.values.end()}}; } - void _Sort() { + void _Sort_and_dedup_if_unique() { _Clear_guard _Guard{this}; _RANGES sort(_View_to_mutate(), value_compare{_Key_compare}); + if constexpr (_IsUnique) { + _Dedup(); + } _Guard._Target = nullptr; } void _Dedup() { - _Clear_guard _Guard{this}; auto _Sorted_view = _View_to_mutate(); auto _Subrange = _RANGES unique(_Sorted_view, [this](const_reference _Left, const_reference _Right) { return this->_Key_equal(_Left.first, _Right.first); @@ -1008,7 +1001,6 @@ private: _Data.values.erase( _Data.values.begin() + static_cast<_RANGES range_difference_t<_MappedContainer>>(_Remaining_count), _Data.values.end()); - _Guard._Target = nullptr; } template From 12e6cd1a42cc7ae06007c7838f679a7b57e34d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 16:11:40 +0100 Subject: [PATCH 08/25] Make newlines between functions in consistent (remove between short repetitive functions) --- stl/inc/flat_set | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stl/inc/flat_set b/stl/inc/flat_set index 8e20f495828..081805e54fb 100644 --- a/stl/inc/flat_set +++ b/stl/inc/flat_set @@ -259,6 +259,7 @@ public: _NODISCARD const_iterator cend() const noexcept { return _Mycont.end(); } + _NODISCARD const_reverse_iterator crbegin() const noexcept { return rbegin(); } @@ -339,7 +340,6 @@ public: void insert_range(_Sorted_t, _Rng&& _Range) { _Insert_range(_STD forward<_Rng>(_Range)); } - void insert(initializer_list<_Kty> _Ilist) { _Insert_range(_Ilist.begin(), _Ilist.end()); } @@ -353,6 +353,7 @@ public: _Clear_guard _Always_clear{this}; return _STD move(_Mycont); } + void replace(container_type&& _Cont) { _STL_ASSERT(_Is_sorted(_Cont), _Msg_not_sorted); _Clear_guard _Guard{this}; @@ -384,6 +385,7 @@ public: _RANGES swap(_Mycomp, _Other._Mycomp); _Guard._Dismiss(); } + void clear() noexcept { _Mycont.clear(); } @@ -781,7 +783,6 @@ _EXPORT_STD template _Container::size_type erase_if(flat_set<_Kty, _Keylt, _Container>& _Val, _Pred _Predicate) { return _Val._Erase_if(_STD _Pass_fn(_Predicate)); } - _EXPORT_STD template _Container::size_type erase_if(flat_multiset<_Kty, _Keylt, _Container>& _Val, _Pred _Predicate) { return _Val._Erase_if(_STD _Pass_fn(_Predicate)); From 63103950d567a853af810cc6460f8a0fcfd97214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 17:35:09 +0100 Subject: [PATCH 09/25] Add precondition checks to flat_map --- stl/inc/flat_map | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index a6829d8b868..ed977d0b7e3 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -299,6 +299,8 @@ private: using _Derived = conditional_t<_IsUnique, flat_map<_Key, _Mapped, _Compare, _KeyContainer, _MappedContainer>, flat_multimap<_Key, _Mapped, _Compare, _KeyContainer, _MappedContainer>>; + static constexpr const char* _Msg_not_sorted = _IsUnique ? "Keys were not sorted-unique!" : "Keys were not sorted!"; + public: using key_type = _Key; using mapped_type = _Mapped; @@ -359,6 +361,7 @@ public: _Flat_map_base( key_container_type _Key_cont, mapped_container_type _Mapped_cont, const key_compare& _Comp = key_compare()) : _Data{.keys = _STD move(_Key_cont), .values = _STD move(_Mapped_cont)}, _Key_compare(_Comp) { + _STL_ASSERT(_Data.keys.size() == _Data.values.size(), "key_cont.size() != mapped_cont.size()"); _Sort_and_dedup_if_unique(); } @@ -378,21 +381,27 @@ public: _Flat_map_base(_Sorted_t, key_container_type _Key_cont, mapped_container_type _Mapped_cont, const key_compare& _Comp = key_compare()) - : _Data{.keys = _STD move(_Key_cont), .values = _STD move(_Mapped_cont)}, _Key_compare(_Comp) {} + : _Data{.keys = _STD move(_Key_cont), .values = _STD move(_Mapped_cont)}, _Key_compare(_Comp) { + _STL_ASSERT(_Data.keys.size() == _Data.values.size(), "key_cont.size() != mapped_cont.size()"); + } template <_Usable_allocator_for _Allocator> _Flat_map_base(_Sorted_t, const key_container_type& _Key_cont, const mapped_container_type& _Mapped_cont, const _Allocator& _Alloc) : _Data{.keys = _STD make_obj_using_allocator(_Alloc, _Key_cont), .values = _STD make_obj_using_allocator(_Alloc, _Mapped_cont)}, - _Key_compare(key_compare()) {} + _Key_compare(key_compare()) { + _STL_ASSERT(_Data.keys.size() == _Data.values.size(), "key_cont.size() != mapped_cont.size()"); + } template <_Usable_allocator_for _Allocator> _Flat_map_base(_Sorted_t, const key_container_type& _Key_cont, const mapped_container_type& _Mapped_cont, const key_compare& _Comp, const _Allocator& _Alloc) : _Data{.keys = _STD make_obj_using_allocator(_Alloc, _Key_cont), .values = _STD make_obj_using_allocator(_Alloc, _Mapped_cont)}, - _Key_compare(_Comp) {} + _Key_compare(_Comp) { + _STL_ASSERT(_Data.keys.size() == _Data.values.size(), "key_cont.size() != mapped_cont.size()"); + } template <_Iterator_for_container _InputIterator> _Flat_map_base(_InputIterator _First, _InputIterator _Last, const key_compare& _Comp = key_compare()) @@ -701,6 +710,8 @@ public: } void replace(key_container_type&& _Key_cont, mapped_container_type&& _Mapped_cont) { + _STL_ASSERT(_Key_cont.size() == _Mapped_cont.size(), "key_cont.size() != mapped_cont.size()"); + _STL_ASSERT(_Is_sorted(_Key_cont), _Msg_not_sorted); _Clear_guard _Guard{this}; _Data.keys = _STD move(_Key_cont); _Data.values = _STD move(_Mapped_cont); @@ -981,6 +992,29 @@ private: _Mutating_iterator{_Data.keys.end(), _Data.values.end()}}; } + template + _NODISCARD bool _Is_sorted( + typename key_container_type::const_iterator _It, const typename key_container_type::const_iterator _End) const { + if constexpr (_IsUnique) { + // sorted-unique + if (_It == _End) { + return true; + } + while (++_It != _End) { + if (!_Key_compare(*(_It - 1), *_It)) { + return false; + } + } + return true; + } else { + return _STD is_sorted(_It, _End, _Key_compare); + } + } + + _NODISCARD bool _Is_sorted(const key_container_type& _Cont) const { + return _Is_sorted(_Cont.begin(), _Cont.end()); + } + void _Sort_and_dedup_if_unique() { _Clear_guard _Guard{this}; _RANGES sort(_View_to_mutate(), value_compare{_Key_compare}); From cd91e3aa729b2c665a6151eae8e0b84910bc5166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 17:35:39 +0100 Subject: [PATCH 10/25] consistently use typedef key_compare instead of template parameter _Compare in flat_map code. --- stl/inc/flat_map | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index ed977d0b7e3..0abe2c1c2ac 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -581,9 +581,9 @@ public: void swap(_Derived& _Other) noexcept(is_nothrow_swappable_v<_KeyContainer> && is_nothrow_swappable_v<_MappedContainer> - && is_nothrow_swappable_v<_Compare>) { + && is_nothrow_swappable_v) { constexpr bool _Is_noexcept = is_nothrow_swappable_v<_KeyContainer> && is_nothrow_swappable_v<_MappedContainer> - && is_nothrow_swappable_v<_Compare>; + && is_nothrow_swappable_v; auto& _Other_base = static_cast<_Flat_map_base&>(_Other); _Flat_map_swap_clear_guard<_Is_noexcept, containers> _Guard{ _STD addressof(_Data), _STD addressof(_Other_base._Data)}; From 359d1aa038309fed6f2126d9447ea45f4e82439d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 17:35:47 +0100 Subject: [PATCH 11/25] Fix formatting. --- stl/inc/flat_map | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index 0abe2c1c2ac..9e70897dd74 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -867,7 +867,8 @@ protected: _MSVC_NO_UNIQUE_ADDRESS key_compare _Key_compare; template - _NODISCARD iterator _Emplace_hint(const const_iterator _Position, _OtherKey&& _Key_val, _MappedArgTypes&&... _Args) { + _NODISCARD iterator _Emplace_hint( + const const_iterator _Position, _OtherKey&& _Key_val, _MappedArgTypes&&... _Args) { _STL_INTERNAL_STATIC_ASSERT(is_constructible_v); _STL_INTERNAL_STATIC_ASSERT(is_same_v, key_type> || (is_constructible_v && _Transparent) ); From 2de5450b8438f957bc3b8a35d3e07a77dccd0027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 21:48:23 +0100 Subject: [PATCH 12/25] _Flat_map_base::_Is_sorted should not be template. --- stl/inc/flat_map | 1 - 1 file changed, 1 deletion(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index 9e70897dd74..eff47f37e72 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -993,7 +993,6 @@ private: _Mutating_iterator{_Data.keys.end(), _Data.values.end()}}; } - template _NODISCARD bool _Is_sorted( typename key_container_type::const_iterator _It, const typename key_container_type::const_iterator _End) const { if constexpr (_IsUnique) { From 571d39e2f27093e2f536a53c92e458e561220236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 22:02:14 +0100 Subject: [PATCH 13/25] Wrap _Flat_map_base::_Key_compare with _Pass_fn when calling standard algorithms (e.g. lower_bound) --- stl/inc/flat_map | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index eff47f37e72..89d48aa49f2 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -1007,7 +1007,7 @@ private: } return true; } else { - return _STD is_sorted(_It, _End, _Key_compare); + return _STD is_sorted(_It, _End, _STD _Pass_fn(_Key_compare)); } } @@ -1123,8 +1123,8 @@ private: _NODISCARD iterator _Lower_bound(const _KeyTy& _Key_val) { _STL_INTERNAL_STATIC_ASSERT(is_same_v<_KeyTy, key_type> || _Transparent); const auto _Key_unchecked_begin = _STD _Get_unwrapped(_STD cbegin(_Data.keys)); - const auto _Key_unchecked_it = - _STD lower_bound(_Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _Key_compare); + const auto _Key_unchecked_it = _STD lower_bound( + _Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); const auto _Dist = _Key_unchecked_it - _Key_unchecked_begin; auto _Key_it = _STD cbegin(_Data.keys); @@ -1138,7 +1138,7 @@ private: _STL_INTERNAL_STATIC_ASSERT(is_same_v<_KeyTy, key_type> || _Transparent); const auto _Key_unchecked_begin = _STD _Get_unwrapped(_STD cbegin(_Data.keys)); const auto _Key_unchecked_it = - _STD lower_bound(_Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _Key_compare); + _STD lower_bound(_Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); const auto _Dist = _Key_unchecked_it - _Key_unchecked_begin; auto _Key_it = _STD cbegin(_Data.keys); @@ -1153,7 +1153,7 @@ private: _STL_INTERNAL_STATIC_ASSERT(is_same_v<_KeyTy, key_type> || _Transparent); const auto _Key_unchecked_begin = _STD _Get_unwrapped(_STD cbegin(_Data.keys)); const auto _Key_unchecked_it = - _STD upper_bound(_Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _Key_compare); + _STD upper_bound(_Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); const auto _Dist = _Key_unchecked_it - _Key_unchecked_begin; auto _Key_it = _STD cbegin(_Data.keys); @@ -1167,7 +1167,7 @@ private: _STL_INTERNAL_STATIC_ASSERT(is_same_v<_KeyTy, key_type> || _Transparent); const auto _Key_unchecked_begin = _STD _Get_unwrapped(_STD cbegin(_Data.keys)); const auto _Key_unchecked_it = - _STD upper_bound(_Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _Key_compare); + _STD upper_bound(_Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); const auto _Dist = _Key_unchecked_it - _Key_unchecked_begin; auto _Key_it = _STD cbegin(_Data.keys); @@ -1408,7 +1408,7 @@ private: template _NODISCARD pair _Try_emplace(_KeyTy&& _Key_val, _MappedArgTypes&&... _Mapped_args) { - auto _Key_it = _STD lower_bound(_Data.keys.begin(), _Data.keys.end(), _Key_val, _Key_compare); + auto _Key_it = _STD lower_bound(_Data.keys.begin(), _Data.keys.end(), _Key_val, _STD _Pass_fn(_Key_compare)); if (_Key_it != _Data.keys.end() && !_Key_compare(_STD forward<_KeyTy>(_Key_val), *_Key_it)) { // Already exists return {this->begin() + (_Key_it - _Data.keys.begin()), false}; @@ -1585,7 +1585,7 @@ private: template _NODISCARD iterator _Emplace_key_mapped(_KeyTy&& _Key_val, _MappedTy&& _Mapped_val) { - const auto _Key_it = _STD upper_bound(_Data.keys.begin(), _Data.keys.end(), _Key_val, _Key_compare); + const auto _Key_it = _STD upper_bound(_Data.keys.begin(), _Data.keys.end(), _Key_val, _STD _Pass_fn(_Key_compare)); const auto _Index = _Key_it - _Data.keys.begin(); { From 25fa9168a23785d5ed315e911f13ec1e23d2e2a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 22:02:52 +0100 Subject: [PATCH 14/25] Correct formatting. --- stl/inc/flat_map | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index 89d48aa49f2..226b76e53bf 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -1123,7 +1123,7 @@ private: _NODISCARD iterator _Lower_bound(const _KeyTy& _Key_val) { _STL_INTERNAL_STATIC_ASSERT(is_same_v<_KeyTy, key_type> || _Transparent); const auto _Key_unchecked_begin = _STD _Get_unwrapped(_STD cbegin(_Data.keys)); - const auto _Key_unchecked_it = _STD lower_bound( + const auto _Key_unchecked_it = _STD lower_bound( _Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); const auto _Dist = _Key_unchecked_it - _Key_unchecked_begin; @@ -1137,8 +1137,8 @@ private: _NODISCARD const_iterator _Lower_bound(const _KeyTy& _Key_val) const { _STL_INTERNAL_STATIC_ASSERT(is_same_v<_KeyTy, key_type> || _Transparent); const auto _Key_unchecked_begin = _STD _Get_unwrapped(_STD cbegin(_Data.keys)); - const auto _Key_unchecked_it = - _STD lower_bound(_Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); + const auto _Key_unchecked_it = _STD lower_bound( + _Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); const auto _Dist = _Key_unchecked_it - _Key_unchecked_begin; auto _Key_it = _STD cbegin(_Data.keys); @@ -1152,8 +1152,8 @@ private: _NODISCARD iterator _Upper_bound(const _KeyTy& _Key_val) { _STL_INTERNAL_STATIC_ASSERT(is_same_v<_KeyTy, key_type> || _Transparent); const auto _Key_unchecked_begin = _STD _Get_unwrapped(_STD cbegin(_Data.keys)); - const auto _Key_unchecked_it = - _STD upper_bound(_Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); + const auto _Key_unchecked_it = _STD upper_bound( + _Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); const auto _Dist = _Key_unchecked_it - _Key_unchecked_begin; auto _Key_it = _STD cbegin(_Data.keys); @@ -1166,8 +1166,8 @@ private: _NODISCARD const_iterator _Upper_bound(const _KeyTy& _Key_val) const { _STL_INTERNAL_STATIC_ASSERT(is_same_v<_KeyTy, key_type> || _Transparent); const auto _Key_unchecked_begin = _STD _Get_unwrapped(_STD cbegin(_Data.keys)); - const auto _Key_unchecked_it = - _STD upper_bound(_Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); + const auto _Key_unchecked_it = _STD upper_bound( + _Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); const auto _Dist = _Key_unchecked_it - _Key_unchecked_begin; auto _Key_it = _STD cbegin(_Data.keys); @@ -1585,8 +1585,9 @@ private: template _NODISCARD iterator _Emplace_key_mapped(_KeyTy&& _Key_val, _MappedTy&& _Mapped_val) { - const auto _Key_it = _STD upper_bound(_Data.keys.begin(), _Data.keys.end(), _Key_val, _STD _Pass_fn(_Key_compare)); - const auto _Index = _Key_it - _Data.keys.begin(); + const auto _Key_it = + _STD upper_bound(_Data.keys.begin(), _Data.keys.end(), _Key_val, _STD _Pass_fn(_Key_compare)); + const auto _Index = _Key_it - _Data.keys.begin(); { _Key _Key_to_insert(_STD forward<_KeyTy>(_Key_val)); From d0a4fa35bf0e79af8c0ef48959150136f0806540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 23:08:08 +0100 Subject: [PATCH 15/25] Introduce a helper function to call _Pass_fn in flat_map. --- stl/inc/flat_map | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index 226b76e53bf..f675720719b 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -866,6 +866,10 @@ protected: containers _Data; _MSVC_NO_UNIQUE_ADDRESS key_compare _Key_compare; + _NODISCARD auto _Pass_key_comp() const noexcept { + return _STD _Pass_fn(_Key_compare); + } + template _NODISCARD iterator _Emplace_hint( const const_iterator _Position, _OtherKey&& _Key_val, _MappedArgTypes&&... _Args) { @@ -1007,7 +1011,7 @@ private: } return true; } else { - return _STD is_sorted(_It, _End, _STD _Pass_fn(_Key_compare)); + return _STD is_sorted(_It, _End, _Pass_key_comp()); } } @@ -1124,7 +1128,7 @@ private: _STL_INTERNAL_STATIC_ASSERT(is_same_v<_KeyTy, key_type> || _Transparent); const auto _Key_unchecked_begin = _STD _Get_unwrapped(_STD cbegin(_Data.keys)); const auto _Key_unchecked_it = _STD lower_bound( - _Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); + _Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _Pass_key_comp()); const auto _Dist = _Key_unchecked_it - _Key_unchecked_begin; auto _Key_it = _STD cbegin(_Data.keys); @@ -1138,7 +1142,7 @@ private: _STL_INTERNAL_STATIC_ASSERT(is_same_v<_KeyTy, key_type> || _Transparent); const auto _Key_unchecked_begin = _STD _Get_unwrapped(_STD cbegin(_Data.keys)); const auto _Key_unchecked_it = _STD lower_bound( - _Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); + _Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _Pass_key_comp()); const auto _Dist = _Key_unchecked_it - _Key_unchecked_begin; auto _Key_it = _STD cbegin(_Data.keys); @@ -1153,7 +1157,7 @@ private: _STL_INTERNAL_STATIC_ASSERT(is_same_v<_KeyTy, key_type> || _Transparent); const auto _Key_unchecked_begin = _STD _Get_unwrapped(_STD cbegin(_Data.keys)); const auto _Key_unchecked_it = _STD upper_bound( - _Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); + _Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _Pass_key_comp()); const auto _Dist = _Key_unchecked_it - _Key_unchecked_begin; auto _Key_it = _STD cbegin(_Data.keys); @@ -1167,7 +1171,7 @@ private: _STL_INTERNAL_STATIC_ASSERT(is_same_v<_KeyTy, key_type> || _Transparent); const auto _Key_unchecked_begin = _STD _Get_unwrapped(_STD cbegin(_Data.keys)); const auto _Key_unchecked_it = _STD upper_bound( - _Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _STD _Pass_fn(_Key_compare)); + _Key_unchecked_begin, _STD _Get_unwrapped(_STD cend(_Data.keys)), _Key_val, _Pass_key_comp()); const auto _Dist = _Key_unchecked_it - _Key_unchecked_begin; auto _Key_it = _STD cbegin(_Data.keys); @@ -1383,6 +1387,7 @@ private: using _Mybase::_Emplace_hint; using _Mybase::_Insert_exact; using _Mybase::_Key_equal; + using _Mybase::_Pass_key_comp; template _NODISCARD mapped_type& _At(const _KeyTy& _Key_val) { @@ -1408,7 +1413,7 @@ private: template _NODISCARD pair _Try_emplace(_KeyTy&& _Key_val, _MappedArgTypes&&... _Mapped_args) { - auto _Key_it = _STD lower_bound(_Data.keys.begin(), _Data.keys.end(), _Key_val, _STD _Pass_fn(_Key_compare)); + auto _Key_it = _STD lower_bound(_Data.keys.begin(), _Data.keys.end(), _Key_val, _Pass_key_comp()); if (_Key_it != _Data.keys.end() && !_Key_compare(_STD forward<_KeyTy>(_Key_val), *_Key_it)) { // Already exists return {this->begin() + (_Key_it - _Data.keys.begin()), false}; @@ -1582,12 +1587,12 @@ private: using _Mybase::_Emplace_hint; using _Mybase::_Insert_exact; using _Mybase::_Key_equal; + using _Mybase::_Pass_key_comp; template _NODISCARD iterator _Emplace_key_mapped(_KeyTy&& _Key_val, _MappedTy&& _Mapped_val) { - const auto _Key_it = - _STD upper_bound(_Data.keys.begin(), _Data.keys.end(), _Key_val, _STD _Pass_fn(_Key_compare)); - const auto _Index = _Key_it - _Data.keys.begin(); + const auto _Key_it = _STD upper_bound(_Data.keys.begin(), _Data.keys.end(), _Key_val, _Pass_key_comp()); + const auto _Index = _Key_it - _Data.keys.begin(); { _Key _Key_to_insert(_STD forward<_KeyTy>(_Key_val)); From 62a3fca1e93b832ee23b31b77d59c36b4eecd7ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Tue, 9 Dec 2025 23:08:39 +0100 Subject: [PATCH 16/25] Start to unify implementation of deduplication between flat_map and flat_set. --- stl/inc/flat_map | 60 ++++++++++++++++++++++++++++++------------------ stl/inc/flat_set | 2 +- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index f675720719b..3ac32034d94 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -362,21 +362,21 @@ public: key_container_type _Key_cont, mapped_container_type _Mapped_cont, const key_compare& _Comp = key_compare()) : _Data{.keys = _STD move(_Key_cont), .values = _STD move(_Mapped_cont)}, _Key_compare(_Comp) { _STL_ASSERT(_Data.keys.size() == _Data.values.size(), "key_cont.size() != mapped_cont.size()"); - _Sort_and_dedup_if_unique(); + _Sort_and_erase_dupes_if_not_multi(); } template <_Usable_allocator_for _Allocator> _Flat_map_base( const key_container_type& _Key_cont, const mapped_container_type& _Mapped_cont, const _Allocator& _Alloc) : _Flat_map_base(_Sorted_t{}, _Key_cont, _Mapped_cont, _Alloc) { - _Sort_and_dedup_if_unique(); + _Sort_and_erase_dupes_if_not_multi(); } template <_Usable_allocator_for _Allocator> _Flat_map_base(const key_container_type& _Key_cont, const mapped_container_type& _Mapped_cont, const key_compare& _Comp, const _Allocator& _Alloc) : _Flat_map_base(_Sorted_t{}, _Key_cont, _Mapped_cont, _Comp, _Alloc) { - _Sort_and_dedup_if_unique(); + _Sort_and_erase_dupes_if_not_multi(); } _Flat_map_base(_Sorted_t, key_container_type _Key_cont, mapped_container_type _Mapped_cont, @@ -1019,26 +1019,43 @@ private: return _Is_sorted(_Cont.begin(), _Cont.end()); } - void _Sort_and_dedup_if_unique() { + void _Sort_and_erase_dupes_if_not_multi() { _Clear_guard _Guard{this}; - _RANGES sort(_View_to_mutate(), value_compare{_Key_compare}); - if constexpr (_IsUnique) { - _Dedup(); - } + + // O(N) if already sorted. + const auto _Begin_unsorted = + _STD is_sorted_until(_STD cbegin(_Data.keys), _STD cend(_Data.keys), _Pass_key_comp()); + const difference_type _Num_sorted = _Begin_unsorted - _STD cbegin(_Data.keys); + + auto _View = _View_to_mutate(); + const auto _Begin = _STD begin(_View); + const auto _Begin_unsorted_values = _Begin + _Num_sorted; + const auto _End = _STD end(_View); + + _STD sort(_Begin_unsorted_values, _End, value_compare{_Key_compare}); + _STD inplace_merge(_Begin, _Begin_unsorted_values, _End, value_compare{_Key_compare}); + + _Erase_dupes_if_not_multi(); + + _STL_INTERNAL_CHECK(_Is_sorted(_Data.keys)); + _Guard._Target = nullptr; } - void _Dedup() { - auto _Sorted_view = _View_to_mutate(); - auto _Subrange = _RANGES unique(_Sorted_view, [this](const_reference _Left, const_reference _Right) { - return this->_Key_equal(_Left.first, _Right.first); - }); - const auto _Remaining_count = _Subrange.begin() - _Sorted_view.begin(); - _Data.keys.erase(_Data.keys.begin() + static_cast<_RANGES range_difference_t<_KeyContainer>>(_Remaining_count), - _Data.keys.end()); - _Data.values.erase( - _Data.values.begin() + static_cast<_RANGES range_difference_t<_MappedContainer>>(_Remaining_count), - _Data.values.end()); + void _Erase_dupes_if_not_multi() { + if constexpr (_IsUnique) { + auto _Sorted_view = _View_to_mutate(); + auto _Subrange = _RANGES unique(_Sorted_view, [this](const_reference _Left, const_reference _Right) { + return this->_Key_equal(_Left.first, _Right.first); + }); + const auto _Remaining_count = _Subrange.begin() - _Sorted_view.begin(); + _Data.keys.erase( + _Data.keys.begin() + static_cast<_RANGES range_difference_t<_KeyContainer>>(_Remaining_count), + _Data.keys.end()); + _Data.values.erase( + _Data.values.begin() + static_cast<_RANGES range_difference_t<_MappedContainer>>(_Remaining_count), + _Data.values.end()); + } } template @@ -1074,9 +1091,8 @@ private: // Merge the newly inserted elements with the existing elements _RANGES inplace_merge(_Sorted_view, _Sorted_view.begin() + _Old_distance, value_compare{_Key_compare}); - if constexpr (_IsUnique) { - _Dedup(); - } + _Erase_dupes_if_not_multi(); + _STL_INTERNAL_CHECK(_Is_sorted(_Data.keys)); _Guard._Target = nullptr; } diff --git a/stl/inc/flat_set b/stl/inc/flat_set index 081805e54fb..8064b408747 100644 --- a/stl/inc/flat_set +++ b/stl/inc/flat_set @@ -690,7 +690,7 @@ private: if constexpr (_NeedSorting) { _STD sort(_Old_end, _End, _Pass_comp()); } else { - _STL_ASSERT(_Is_sorted(_Old_end, _End), _Msg_not_sorted); + _STL_INTERNAL_CHECK(_Is_sorted(_Old_end, _End)); } _STD inplace_merge(_Begin, _Old_end, _End, _Pass_comp()); From 0f1b93b922035364e7dbb509438bee94bdde2a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Wed, 10 Dec 2025 00:36:45 +0100 Subject: [PATCH 17/25] Make begin/end available when including --- stl/inc/flat_map | 1 + 1 file changed, 1 insertion(+) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index 3ac32034d94..b83c138d389 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -19,6 +19,7 @@ _EMIT_STL_WARNING(STL4038, "The contents of are available only with C #include #include #include +#include #pragma pack(push, _CRT_PACKING) #pragma warning(push, _STL_WARNING_LEVEL) From 3f5513ec3e8ce27e7695ddf0297a98148ad16224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Wed, 10 Dec 2025 00:37:04 +0100 Subject: [PATCH 18/25] Reference correct standard WP in comments. --- stl/inc/flat_map | 2 +- stl/inc/flat_set | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index b83c138d389..ef7504baf8f 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -698,7 +698,7 @@ public: _NODISCARD containers extract() && noexcept( is_nothrow_move_constructible_v && is_nothrow_move_constructible_v) /* strengthened */ { - // always clears the container (N4950 [flat.map.modifiers]/33 and [flat.multimap.overview]/4 + // always clears the container (N5014 [flat.map.modifiers]/33 and [flat.multimap.overview]/4 _Clear_guard _Always_clear{this}; return _STD move(_Data); } diff --git a/stl/inc/flat_set b/stl/inc/flat_set index 8064b408747..bb940f99437 100644 --- a/stl/inc/flat_set +++ b/stl/inc/flat_set @@ -651,7 +651,7 @@ private: template _NODISCARD size_type _Erase_if(_Pred _Predicate) { - // Maintain invariants when an exception is thrown (N4950 [flat.set.erasure]/5, [flat.multiset.erasure]/5) + // Maintain invariants when an exception is thrown (N5014 [flat.set.erasure]/5, [flat.multiset.erasure]/5) _Clear_guard _Guard{this}; const auto _Erased_count = _STD _Erase_remove_if(_Mycont, _Predicate); _Guard._Target = nullptr; From 7d56088c8e0dc7d6157f64c0126585729b218f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Wed, 10 Dec 2025 00:38:05 +0100 Subject: [PATCH 19/25] Correct formatting. --- stl/inc/flat_map | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index ef7504baf8f..798ba4be9b7 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -1028,10 +1028,10 @@ private: _STD is_sorted_until(_STD cbegin(_Data.keys), _STD cend(_Data.keys), _Pass_key_comp()); const difference_type _Num_sorted = _Begin_unsorted - _STD cbegin(_Data.keys); - auto _View = _View_to_mutate(); - const auto _Begin = _STD begin(_View); + auto _View = _View_to_mutate(); + const auto _Begin = _STD begin(_View); const auto _Begin_unsorted_values = _Begin + _Num_sorted; - const auto _End = _STD end(_View); + const auto _End = _STD end(_View); _STD sort(_Begin_unsorted_values, _End, value_compare{_Key_compare}); _STD inplace_merge(_Begin, _Begin_unsorted_values, _End, value_compare{_Key_compare}); From 5a5fc469aa50aaee1b7f81f9557f18de4a6a0fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Wed, 10 Dec 2025 01:23:33 +0100 Subject: [PATCH 20/25] Use ranges::sort instead of std::sort. --- stl/inc/flat_map | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index 798ba4be9b7..0539e1c8bb0 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -1033,8 +1033,8 @@ private: const auto _Begin_unsorted_values = _Begin + _Num_sorted; const auto _End = _STD end(_View); - _STD sort(_Begin_unsorted_values, _End, value_compare{_Key_compare}); - _STD inplace_merge(_Begin, _Begin_unsorted_values, _End, value_compare{_Key_compare}); + _RANGES sort(_Begin_unsorted_values, _End, value_compare{_Key_compare}); + _RANGES inplace_merge(_Begin, _Begin_unsorted_values, _End, value_compare{_Key_compare}); _Erase_dupes_if_not_multi(); From f5eb3289a270acd19be49a682759f7a8db4bf3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Michal?= Date: Wed, 10 Dec 2025 13:59:59 +0100 Subject: [PATCH 21/25] Mention that FAILed libcxx tests are due to different resolution of LWG-2227 --- tests/libcxx/expected_results.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 606259690c5..4cf2f91466d 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -268,6 +268,7 @@ std/strings/c.strings/cuchar.compile.pass.cpp FAIL # P0429R9 +# MSVC's STL and libc++ speculatively implement LWG-2227 (not yet resolved) in different ways # static_assert(!std::is_nothrow_move_constructible_v) with C = std::flat_(multi)map std/containers/container.adaptors/flat.multimap/flat.multimap.cons/move_noexcept.pass.cpp FAIL std/containers/container.adaptors/flat.map/flat.map.cons/move_noexcept.pass.cpp FAIL From fe77c32d531944cc65eda3d3af6f86c3ec373f03 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Fri, 12 Dec 2025 08:22:51 -0800 Subject: [PATCH 22/25] Fix/update citations. --- stl/inc/flat_map | 2 +- stl/inc/flat_set | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index 0539e1c8bb0..e43facb3d98 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -698,7 +698,7 @@ public: _NODISCARD containers extract() && noexcept( is_nothrow_move_constructible_v && is_nothrow_move_constructible_v) /* strengthened */ { - // always clears the container (N5014 [flat.map.modifiers]/33 and [flat.multimap.overview]/4 + // always clears the container (N5014 [flat.map.modifiers]/34 and [flat.multimap.overview]/4) _Clear_guard _Always_clear{this}; return _STD move(_Data); } diff --git a/stl/inc/flat_set b/stl/inc/flat_set index bb940f99437..77f3a768797 100644 --- a/stl/inc/flat_set +++ b/stl/inc/flat_set @@ -349,7 +349,7 @@ public: _NODISCARD container_type extract() && noexcept( is_nothrow_move_constructible_v) /* strengthened */ { - // always clears the container (N4950 [flat.set.modifiers]/14 and [flat.multiset.modifiers]/10) + // always clears the container (N5014 [flat.set.modifiers]/14 and [flat.multiset.modifiers]/10) _Clear_guard _Always_clear{this}; return _STD move(_Mycont); } From af4f932769c445ce0decd65b38da46401bc9c746 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Fri, 12 Dec 2025 08:35:27 -0800 Subject: [PATCH 23/25] Consistently use the `_Always_clear` name. --- stl/inc/flat_map | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index e43facb3d98..b8fcb94dbb7 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -705,7 +705,7 @@ public: template _NODISCARD containers _Extract_using_allocator(const _Allocator& _Alloc) && { - _Clear_guard _Guard{this}; + _Clear_guard _Always_clear{this}; return containers{.keys = _STD make_obj_using_allocator(_Alloc, _STD move(_Data.keys)), .values = _STD make_obj_using_allocator(_Alloc, _STD move(_Data.values))}; } From 9d303779e7db1bb1d5f988057518e0e09b4ec774 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Fri, 12 Dec 2025 08:53:01 -0800 Subject: [PATCH 24/25] All shall love `adjacent_find()` and despair. --- stl/inc/flat_map | 11 ++--------- stl/inc/flat_set | 11 ++--------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/stl/inc/flat_map b/stl/inc/flat_map index b8fcb94dbb7..1e6eac4961b 100644 --- a/stl/inc/flat_map +++ b/stl/inc/flat_map @@ -1002,15 +1002,8 @@ private: typename key_container_type::const_iterator _It, const typename key_container_type::const_iterator _End) const { if constexpr (_IsUnique) { // sorted-unique - if (_It == _End) { - return true; - } - while (++_It != _End) { - if (!_Key_compare(*(_It - 1), *_It)) { - return false; - } - } - return true; + auto _Negated = [this](const key_type& _Lhs, const key_type& _Rhs) { return !_Key_compare(_Lhs, _Rhs); }; + return _STD adjacent_find(_It, _End, _Negated) == _End; } else { return _STD is_sorted(_It, _End, _Pass_key_comp()); } diff --git a/stl/inc/flat_set b/stl/inc/flat_set index 77f3a768797..08fbf79e031 100644 --- a/stl/inc/flat_set +++ b/stl/inc/flat_set @@ -477,15 +477,8 @@ private: _NODISCARD bool _Is_sorted(const_iterator _It, const const_iterator _End) const { if constexpr (_IsUnique) { // sorted-unique - if (_It == _End) { - return true; - } - while (++_It != _End) { - if (!_Compare(*(_It - 1), *_It)) { - return false; - } - } - return true; + auto _Negated = [this](const key_type& _Lhs, const key_type& _Rhs) { return !_Compare(_Lhs, _Rhs); }; + return _STD adjacent_find(_It, _End, _Negated) == _End; } else { return _STD is_sorted(_It, _End, _Pass_comp()); } From c8b7bf1ca44abd2da0205cc5b4477fd1328d7166 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Fri, 12 Dec 2025 08:58:32 -0800 Subject: [PATCH 25/25] Categorize LWG-2227 divergence as "likely bogus tests", not "missing STL features". --- tests/libcxx/expected_results.txt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index 808a6946c56..f3b52a90def 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -266,13 +266,6 @@ std/input.output/syncstream/osyncstream/thread/several_threads.pass.cpp:1 SKIPPE std/depr/depr.c.headers/uchar_h.compile.pass.cpp FAIL std/strings/c.strings/cuchar.compile.pass.cpp FAIL -# P0429R9 - -# MSVC's STL and libc++ speculatively implement LWG-2227 (not yet resolved) in different ways -# static_assert(!std::is_nothrow_move_constructible_v) with C = std::flat_(multi)map -std/containers/container.adaptors/flat.multimap/flat.multimap.cons/move_noexcept.pass.cpp FAIL -std/containers/container.adaptors/flat.map/flat.map.cons/move_noexcept.pass.cpp FAIL - # P0533R9 constexpr For And std/language.support/support.limits/support.limits.general/cmath.version.compile.pass.cpp FAIL std/language.support/support.limits/support.limits.general/cstdlib.version.compile.pass.cpp FAIL @@ -800,6 +793,11 @@ std/time/time.clock/time.clock.file/to_from_sys.pass.cpp FAIL # libc++'s filesystem::path::iterator models bidirectional_iterator, which is not guaranteed by the Standard std/input.output/filesystems/class.path/range_concept_conformance.compile.pass.cpp FAIL +# MSVC's STL and libc++ speculatively implement LWG-2227 (not yet resolved) in different ways. +# static_assert(!std::is_nothrow_move_constructible_v) with C = std::flat_(multi)map +std/containers/container.adaptors/flat.map/flat.map.cons/move_noexcept.pass.cpp FAIL +std/containers/container.adaptors/flat.multimap/flat.multimap.cons/move_noexcept.pass.cpp FAIL + # libc++ speculatively implemented an old proposed resolution for LWG-3645. # This test is bogus according to the wording that was ultimately accepted for C++23. std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp FAIL