From 538400dda205b5569a1ff5f90e88b214fe92f660 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Sun, 21 Dec 2025 18:59:39 +0800 Subject: [PATCH 1/2] Make `flat_(multi)set` insertion exception-safe for non-standard sequence containers Per N5032 [container.reqmts]/66.1, exception safety of single element insertion is only guaranteed for standard containers. So perhaps we should guard the insertion for non-standard containers that do not have guaranteed (exception-safe) `emplace`. The `emplace` calls are only guarded for containers whose `emplace` have no guaranteed exception safety. Currently, all supported standard containers have such exception safety, so this only affects non-standard containers. --- stl/inc/flat_set | 24 ++++++++++++++++++------ stl/inc/xmemory | 8 ++++++-- tests/libcxx/expected_results.txt | 11 ++--------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/stl/inc/flat_set b/stl/inc/flat_set index 08fbf79e031..2fe6a1811c0 100644 --- a/stl/inc/flat_set +++ b/stl/inc/flat_set @@ -57,6 +57,18 @@ struct _NODISCARD _Flat_set_swap_clear_guard { void _Dismiss() noexcept {} }; +template +_NODISCARD auto _Emplace_with_clear_guard(_Container& _Cont, typename _Container::const_iterator _Where, _Ty&& _Val) { + if constexpr (_Has_guaranteed_single_insertion<_Container>) { + return _Cont.emplace(_Where, _STD forward<_Ty>(_Val)); + } else { + _Clear_guard _Guard{_STD addressof(_Cont)}; + auto _Iter = _Cont.emplace(_Where, _STD forward<_Ty>(_Val)); + _Guard._Target = nullptr; + return _Iter; + } +} + _EXPORT_STD template , class _Container = vector<_Kty>> class flat_set; @@ -516,7 +528,7 @@ private: if constexpr (is_same_v, _Kty>) { _STL_INTERNAL_CHECK(_Can_insert(_Where, _Val)); - return pair{_Mycont.emplace(_Where, _STD forward<_Ty>(_Val)), true}; + return pair{_STD _Emplace_with_clear_guard(_Mycont, _Where, _STD forward<_Ty>(_Val)), true}; } else { // heterogeneous insertion // FIXME: The standard only requires `find(_Val) == find(_Keyval)` (per N4958 [flat.set.modifiers]/2), @@ -526,11 +538,11 @@ private: _STL_ASSERT(_Can_insert(_Where, _Keyval), "The conversion from the heterogeneous key to key_type should " "be consistent with the heterogeneous lookup!"); - return pair{_Mycont.emplace(_Where, _STD move(_Keyval)), true}; + return pair{_STD _Emplace_with_clear_guard(_Mycont, _Where, _STD move(_Keyval)), true}; } } else { _STL_INTERNAL_STATIC_ASSERT(is_same_v, _Kty>); - return _Mycont.emplace(upper_bound(_Val), _STD forward<_Ty>(_Val)); + return _STD _Emplace_with_clear_guard(_Mycont, upper_bound(_Val), _STD forward<_Ty>(_Val)); } } @@ -560,7 +572,7 @@ private: if constexpr (is_same_v, _Kty>) { _STL_INTERNAL_CHECK(_Can_insert(_Where, _Val)); - return _Mycont.emplace(_Where, _STD forward<_Ty>(_Val)); + return _STD _Emplace_with_clear_guard(_Mycont, _Where, _STD forward<_Ty>(_Val)); } else { // heterogeneous insertion // FIXME: The standard only requires `find(_Val) == find(_Keyval)` (per N4958 [flat.set.modifiers]/2), @@ -570,7 +582,7 @@ private: _STL_ASSERT(_Can_insert(_Where, _Keyval), "The conversion from the heterogeneous key to key_type should " "be consistent with the heterogeneous lookup!"); - return _Mycont.emplace(_Where, _STD move(_Keyval)); + return _STD _Emplace_with_clear_guard(_Mycont, _Where, _STD move(_Keyval)); } } else { _STL_INTERNAL_STATIC_ASSERT(is_same_v, _Kty>); @@ -591,7 +603,7 @@ private: _Where = _STD lower_bound(_Where, _End, _Val, _Pass_comp()); } - return _Mycont.emplace(_Where, _STD forward<_Ty>(_Val)); + return _STD _Emplace_with_clear_guard(_Mycont, _Where, _STD forward<_Ty>(_Val)); } } diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 66d33446009..80dc6c715c0 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2762,10 +2762,14 @@ namespace ranges { } // namespace ranges template -constexpr bool _Has_guaranteed_push_back = false; // N5014 [sequence.reqmts]/104, /108; used by flat_(multi)map::insert. +constexpr bool _Has_guaranteed_push_back = false; // N5032 [sequence.reqmts]/104, /108; used by flat_(multi)map::insert. + +// N5032 [container.reqmts]/66.1; used by various flat_(multi)set inserting functions. +template +constexpr bool _Has_guaranteed_single_insertion = _Has_guaranteed_push_back<_Ty>; template -constexpr bool _Has_guaranteed_append_range = false; // N5014 [sequence.reqmts]/112; used by flat_(multi)set::insert. +constexpr bool _Has_guaranteed_append_range = false; // N5032 [sequence.reqmts]/112; used by flat_(multi)set::insert. #endif // _HAS_CXX23 template diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index f3b52a90def..7911c54a3fc 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -51,15 +51,6 @@ std/strings/basic.string/string.modifiers/string_append/initializer_list.pass.cp std/strings/basic.string/string.modifiers/string_assign/string.pass.cpp:0 FAIL std/strings/basic.string/string.modifiers/string_assign/string.pass.cpp:1 FAIL -# LLVM-140448: [libc++][test] flat_set std test uses EmplaceUnsafeContainer which is non-standard -std/containers/container.adaptors/flat.set/flat.set.modifiers/emplace_hint.pass.cpp FAIL -std/containers/container.adaptors/flat.set/flat.set.modifiers/emplace.pass.cpp FAIL -std/containers/container.adaptors/flat.set/flat.set.modifiers/insert_cv.pass.cpp FAIL -std/containers/container.adaptors/flat.set/flat.set.modifiers/insert_iter_cv.pass.cpp FAIL -std/containers/container.adaptors/flat.set/flat.set.modifiers/insert_iter_rv.pass.cpp FAIL -std/containers/container.adaptors/flat.set/flat.set.modifiers/insert_rv.pass.cpp FAIL -std/containers/container.adaptors/flat.set/flat.set.modifiers/insert_transparent.pass.cpp FAIL - # LLVM-158302: Clang 20 i686-pc-windows-msvc regression, silent bad codegen for std::current_exception() # SKIPPED because this is x86-specific. std/language.support/support.exception/except.nested/assign.pass.cpp:2 SKIPPED @@ -435,6 +426,8 @@ std/containers/container.adaptors/flat.set/flat.set.modifiers/insert_iter_iter.p std/containers/container.adaptors/flat.set/flat.set.modifiers/insert_iter_iter.pass.cpp:1 FAIL std/containers/container.adaptors/flat.set/flat.set.modifiers/insert_range.pass.cpp:0 FAIL std/containers/container.adaptors/flat.set/flat.set.modifiers/insert_range.pass.cpp:1 FAIL +std/containers/container.adaptors/flat.set/flat.set.modifiers/insert_transparent.pass.cpp:0 FAIL +std/containers/container.adaptors/flat.set/flat.set.modifiers/insert_transparent.pass.cpp:1 FAIL std/containers/container.adaptors/flat.set/flat.set.observers/comp.pass.cpp:0 FAIL std/containers/container.adaptors/flat.set/flat.set.observers/comp.pass.cpp:1 FAIL std/containers/container.adaptors/flat.set/flat.set.operations/contains_transparent.pass.cpp:0 FAIL From 8b629c12c65ccab3189a49cddc1a35f51f08b871 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 5 Jan 2026 05:28:13 -0800 Subject: [PATCH 2/2] Update and move FIXME comment down to "erase". --- 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 2fe6a1811c0..496bf624fea 100644 --- a/stl/inc/flat_set +++ b/stl/inc/flat_set @@ -291,8 +291,6 @@ public: } // modifiers - // FIXME, the "insert" and "erase" methods may not be able to restore the invariant, if the underlying - // container is unable to provide strong guarantee for "erase" and "insert" methods. template auto emplace(_Args&&... _Vals) { constexpr bool _Is_key_type = _In_place_key_extract_set<_Kty, remove_cvref_t<_Args>...>::_Extractable; @@ -373,6 +371,9 @@ public: _Guard._Target = nullptr; } + // FIXME, the "erase" member functions need clear guards to restore the invariant when the underlying + // container doesn't provide the strong guarantee for its "erase" member functions. + // NB: `erase(iterator)` is identical to `erase(const_iterator)` iterator erase(const_iterator _Where) { return _Mycont.erase(_Where);