[libcxx-commits] [libcxx] [libc++] Don't claim strong emplace guarantee for vector/deque of throwing-move types (PR #229686)

Michiel van Slobbe via libcxx-commits libcxx-commits at lists.llvm.org
Sat Oct 17 14:44:57 PDT 2026


https://github.com/mvanslobbe updated https://github.com/llvm/llvm-project/pull/229686

>From 632501f1a06550a19101aa96d37d541234e0154d Mon Sep 17 00:00:00 2001
From: Michiel van Slobbe <michiel.van.slobbe at gmail.com>
Date: Wed, 7 Oct 2026 09:08:11 +0200
Subject: [PATCH 1/2] [libc++] Don't claim strong emplace guarantee for
 vector/deque of throwing-move types

__container_traits<vector> and __container_traits<deque> reported
__emplacement_has_strong_exception_safety_guarantee whenever T is
Cpp17CopyInsertable. [vector.modifiers]/2 and [deque.modifiers]/3 only
give the "no effects" guarantee for an insertion at the end (or at either
end for deque). An insertion in the middle move-constructs and
move-assigns existing elements, and exceptions from those operations are
explicitly excluded.

flat_map, flat_multimap, flat_set and flat_multiset use the trait to
decide whether to clear themselves when an emplacement throws. With the
wrong answer they skipped the clear, so a throwing assignment, or a
throwing copy during reallocation, left flat_map with keys and values of
different sizes and flat_set with duplicate keys.

Only report the strong guarantee when T is nothrow move constructible
and nothrow move assignable. In that case the only operations that can
throw (allocation and constructing the new element) happen before the
container is modified.
---
 libcxx/include/__vector/container_traits.h    |   9 +-
 libcxx/include/deque                          |   6 +-
 .../container_traits.compile.pass.cpp         |   8 +-
 .../emplace_throwing_element.pass.cpp         | 136 ++++++++++++++++++
 .../emplace_throwing_element.pass.cpp         |  88 ++++++++++++
 5 files changed, 239 insertions(+), 8 deletions(-)
 create mode 100644 libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace_throwing_element.pass.cpp
 create mode 100644 libcxx/test/std/containers/container.adaptors/flat.set/flat.set.modifiers/emplace_throwing_element.pass.cpp

diff --git a/libcxx/include/__vector/container_traits.h b/libcxx/include/__vector/container_traits.h
index 20f2b9ef042cbd..16711682e08fff 100644
--- a/libcxx/include/__vector/container_traits.h
+++ b/libcxx/include/__vector/container_traits.h
@@ -11,9 +11,8 @@
 
 #include <__config>
 #include <__fwd/vector.h>
-#include <__memory/allocator_traits.h>
 #include <__type_traits/container_traits.h>
-#include <__type_traits/disjunction.h>
+#include <__type_traits/is_nothrow_assignable.h>
 #include <__type_traits/is_nothrow_constructible.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -30,8 +29,12 @@ struct __container_traits<vector<_Tp, _Allocator> > {
   //  inserting a single element at the end and T is Cpp17CopyInsertable or is_nothrow_move_constructible_v<T> is true,
   //  there are no effects. Otherwise, if an exception is thrown by the move constructor of a non-Cpp17CopyInsertable T,
   //  the effects are unspecified.
+  //
+  // The "no effects" guarantee for an insertion at the end does not cover an insertion in the middle, which
+  // move-constructs and move-assigns existing elements. Emplacement at an arbitrary position therefore has the
+  // strong guarantee only when neither of those operations can throw.
   static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee =
-      is_nothrow_move_constructible<_Tp>::value || __is_cpp17_copy_insertable_v<_Allocator>;
+      is_nothrow_move_constructible<_Tp>::value && is_nothrow_move_assignable<_Tp>::value;
 
   static _LIBCPP_CONSTEXPR const bool __reservable = true;
 };
diff --git a/libcxx/include/deque b/libcxx/include/deque
index ba4d00f81e95e7..c42e285f30f14d 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -2525,8 +2525,12 @@ struct __container_traits<deque<_Tp, _Allocator> > {
   //  assignment operator of T, there are no effects. If an exception is thrown while inserting a single element at
   //  either end, there are no effects. Otherwise, if an exception is thrown by the move constructor of a
   //  non-Cpp17CopyInsertable T, the effects are unspecified.
+  //
+  // The "no effects" guarantee for an insertion at the end does not cover an insertion in the middle, which
+  // move-constructs and move-assigns existing elements. Emplacement at an arbitrary position therefore has the
+  // strong guarantee only when neither of those operations can throw.
   static _LIBCPP_CONSTEXPR const bool __emplacement_has_strong_exception_safety_guarantee =
-      is_nothrow_move_constructible<_Tp>::value || __is_cpp17_copy_insertable_v<_Allocator>;
+      is_nothrow_move_constructible<_Tp>::value && is_nothrow_move_assignable<_Tp>::value;
 
   static _LIBCPP_CONSTEXPR const bool __reservable = false;
 };
diff --git a/libcxx/test/libcxx/containers/container_traits.compile.pass.cpp b/libcxx/test/libcxx/containers/container_traits.compile.pass.cpp
index 1452bfbaf3960e..ba1894a33cb4ce 100644
--- a/libcxx/test/libcxx/containers/container_traits.compile.pass.cpp
+++ b/libcxx/test/libcxx/containers/container_traits.compile.pass.cpp
@@ -77,14 +77,14 @@ void test() {
 
   check<true, std::deque<int> >();
   check<true, std::deque<int, test_allocator<int> > >();
-  check<true, std::deque<MoveOnly> >();
-  check<true, std::deque<ThrowOnMove> >();
+  check<false, std::deque<MoveOnly> >();
+  check<false, std::deque<ThrowOnMove> >();
   check<false, std::deque<NonCopyThrowOnMove> >();
 
   check<true, std::vector<int> >();
   check<true, std::vector<int, test_allocator<int> > >();
-  check<true, std::vector<MoveOnly> >();
-  check<true, std::vector<ThrowOnMove> >();
+  check<false, std::vector<MoveOnly> >();
+  check<false, std::vector<ThrowOnMove> >();
   check<false, std::vector<NonCopyThrowOnMove> >();
 
   check<true, std::set<int> >();
diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace_throwing_element.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace_throwing_element.pass.cpp
new file mode 100644
index 00000000000000..72c763407857d1
--- /dev/null
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace_throwing_element.pass.cpp
@@ -0,0 +1,136 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: no-exceptions
+
+// <flat_map>
+
+// Test that the invariants are restored when inserting an element in the middle of the
+// underlying containers throws from the element's assignment operator or, during
+// reallocation, from its copy constructor. Neither has a "no effects" guarantee for an
+// insertion in the middle of a vector or deque, even if the element type is
+// Cpp17CopyInsertable.
+
+#include <cassert>
+#include <deque>
+#include <flat_map>
+#include <functional>
+#include <type_traits>
+#include <vector>
+
+#include "test_macros.h"
+#include "../helpers.h"
+
+struct ThrowingAssign {
+  static inline bool throw_on_assign = false;
+
+  int value;
+
+  ThrowingAssign(int v) : value(v) {}
+  ThrowingAssign(const ThrowingAssign&)     = default;
+  ThrowingAssign(ThrowingAssign&&) noexcept = default;
+  ThrowingAssign& operator=(const ThrowingAssign& other) {
+    if (throw_on_assign)
+      throw 42;
+    value = other.value;
+    return *this;
+  }
+  ThrowingAssign& operator=(ThrowingAssign&& other) {
+    if (throw_on_assign)
+      throw 42;
+    value = other.value;
+    return *this;
+  }
+
+  friend bool operator==(const ThrowingAssign&, const ThrowingAssign&)  = default;
+  friend auto operator<=>(const ThrowingAssign&, const ThrowingAssign&) = default;
+};
+
+struct ThrowingCopy {
+  static inline int copies_until_throw = -1; // -1: never throw
+
+  int value;
+
+  ThrowingCopy(int v) : value(v) {}
+  ThrowingCopy(const ThrowingCopy& other) : value(other.value) {
+    if (copies_until_throw >= 0 && copies_until_throw-- == 0)
+      throw 42;
+  }
+  ThrowingCopy(ThrowingCopy&& other) : ThrowingCopy(other) {}
+  ThrowingCopy& operator=(const ThrowingCopy&) noexcept = default;
+  ThrowingCopy& operator=(ThrowingCopy&&) noexcept      = default;
+
+  friend bool operator==(const ThrowingCopy&, const ThrowingCopy&)  = default;
+  friend auto operator<=>(const ThrowingCopy&, const ThrowingCopy&) = default;
+};
+
+// n >= 0: assignments throw, and the n-th copy (counting from 0) throws. n < 0: nothing throws.
+void set_throwing(int n) {
+  ThrowingAssign::throw_on_assign  = n >= 0;
+  ThrowingCopy::copies_until_throw = n;
+}
+
+template <class Container>
+Container make_container(std::initializer_list<int> il) {
+  Container c;
+  for (int v : il)
+    c.emplace_back(v);
+  if constexpr (requires { c.reserve(8); }) {
+    // ThrowingAssign: spare capacity makes vector insert in place.
+    // ThrowingCopy: no spare capacity makes vector reallocate.
+    if constexpr (std::is_same_v<typename Container::value_type, ThrowingCopy>)
+      c.shrink_to_fit();
+    else
+      c.reserve(8);
+  }
+  return c;
+}
+
+template <class KeyContainer, class ValueContainer, class F>
+void test_one(F insert_function) {
+  using Key   = typename KeyContainer::value_type;
+  using Value = typename ValueContainer::value_type;
+  using M     = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
+
+  for (int n = 0; n < 6; ++n) {
+    M m(std::sorted_unique, make_container<KeyContainer>({1, 3, 5}), make_container<ValueContainer>({10, 30, 50}));
+    set_throwing(n);
+    try {
+      insert_function(m);
+    } catch (int) {
+    }
+    set_throwing(-1);
+    check_invariant(m);
+  }
+}
+
+template <class KeyContainer, class ValueContainer>
+void test() {
+  test_one<KeyContainer, ValueContainer>([](auto& m) { m.emplace(2, 20); });
+  test_one<KeyContainer, ValueContainer>([](auto& m) { m.emplace_hint(m.begin() + 1, 2, 20); });
+  test_one<KeyContainer, ValueContainer>([](auto& m) { m.try_emplace(2, 20); });
+  test_one<KeyContainer, ValueContainer>([](auto& m) { m.try_emplace(m.begin() + 1, 2, 20); });
+  test_one<KeyContainer, ValueContainer>([](auto& m) { m.insert({2, 20}); });
+  test_one<KeyContainer, ValueContainer>([](auto& m) { m.insert(m.begin() + 1, {2, 20}); });
+  test_one<KeyContainer, ValueContainer>([](auto& m) { m.insert_or_assign(2, 20); });
+}
+
+int main(int, char**) {
+  // Throw while inserting the key.
+  test<std::vector<ThrowingAssign>, std::vector<int>>();
+  test<std::deque<ThrowingAssign>, std::vector<int>>();
+  // Throw while inserting the mapped value.
+  test<std::vector<int>, std::vector<ThrowingAssign>>();
+  test<std::vector<int>, std::deque<ThrowingAssign>>();
+  // Throw while reallocating.
+  test<std::vector<ThrowingCopy>, std::vector<int>>();
+  test<std::vector<int>, std::vector<ThrowingCopy>>();
+
+  return 0;
+}
diff --git a/libcxx/test/std/containers/container.adaptors/flat.set/flat.set.modifiers/emplace_throwing_element.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.set/flat.set.modifiers/emplace_throwing_element.pass.cpp
new file mode 100644
index 00000000000000..99ec437d5a173b
--- /dev/null
+++ b/libcxx/test/std/containers/container.adaptors/flat.set/flat.set.modifiers/emplace_throwing_element.pass.cpp
@@ -0,0 +1,88 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: no-exceptions
+
+// <flat_set>
+
+// Test that the invariants are restored when inserting an element in the middle of the
+// underlying container throws from the element's assignment operator. An insertion in the
+// middle of a vector or deque has no "no effects" guarantee in that case, even if the
+// element type is Cpp17CopyInsertable.
+
+#include <cassert>
+#include <deque>
+#include <flat_set>
+#include <functional>
+#include <vector>
+
+#include "test_macros.h"
+#include "../helpers.h"
+
+struct ThrowingAssign {
+  static inline bool throw_on_assign = false;
+
+  int value;
+
+  ThrowingAssign(int v) : value(v) {}
+  ThrowingAssign(const ThrowingAssign&)     = default;
+  ThrowingAssign(ThrowingAssign&&) noexcept = default;
+  ThrowingAssign& operator=(const ThrowingAssign& other) {
+    if (throw_on_assign)
+      throw 42;
+    value = other.value;
+    return *this;
+  }
+  ThrowingAssign& operator=(ThrowingAssign&& other) {
+    if (throw_on_assign)
+      throw 42;
+    value = other.value;
+    return *this;
+  }
+
+  friend bool operator==(const ThrowingAssign&, const ThrowingAssign&)  = default;
+  friend auto operator<=>(const ThrowingAssign&, const ThrowingAssign&) = default;
+};
+
+template <class KeyContainer, class F>
+void test_one(F insert_function) {
+  using M = std::flat_set<ThrowingAssign, std::less<ThrowingAssign>, KeyContainer>;
+
+  KeyContainer keys;
+  // Spare capacity makes vector insert in place instead of reallocating.
+  if constexpr (requires { keys.reserve(8); })
+    keys.reserve(8);
+  for (int v : {1, 3, 5})
+    keys.emplace_back(v);
+  M m(std::sorted_unique, std::move(keys));
+
+  ThrowingAssign::throw_on_assign = true;
+  try {
+    insert_function(m);
+    assert(false);
+  } catch (int) {
+  }
+  ThrowingAssign::throw_on_assign = false;
+  check_invariant(m);
+}
+
+template <class KeyContainer>
+void test() {
+  test_one<KeyContainer>([](auto& m) { m.emplace(2); });
+  test_one<KeyContainer>([](auto& m) { m.emplace_hint(m.begin() + 1, 2); });
+  test_one<KeyContainer>([](auto& m) { m.insert(ThrowingAssign(2)); });
+  test_one<KeyContainer>([](auto& m) { m.insert(m.begin() + 1, ThrowingAssign(2)); });
+}
+
+int main(int, char**) {
+  test<std::vector<ThrowingAssign>>();
+  test<std::deque<ThrowingAssign>>();
+
+  return 0;
+}

>From 365e8b1b7328bba04decb9d49616945066340497 Mon Sep 17 00:00:00 2001
From: Michiel van Slobbe <michiel.van.slobbe at gmail.com>
Date: Sat, 17 Oct 2026 23:44:46 +0200
Subject: [PATCH 2/2] [libc++] Pass throw state to test elements explicitly

Address review feedback: the throwing element types in the new
emplace_throwing_element tests now keep a pointer to state owned by
each test case instead of using global flags, so nothing has to be
reset between test points. An enum replaces the -1 "never throw"
sentinel in the flat_map test.
---
 .../emplace_throwing_element.pass.cpp         | 94 ++++++++++++-------
 .../emplace_throwing_element.pass.cpp         | 31 +++---
 2 files changed, 77 insertions(+), 48 deletions(-)

diff --git a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace_throwing_element.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace_throwing_element.pass.cpp
index 72c763407857d1..66d5bd1f1c8d3d 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace_throwing_element.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.map/flat.map.modifiers/emplace_throwing_element.pass.cpp
@@ -18,6 +18,7 @@
 // Cpp17CopyInsertable.
 
 #include <cassert>
+#include <compare>
 #include <deque>
 #include <flat_map>
 #include <functional>
@@ -27,64 +28,76 @@
 #include "test_macros.h"
 #include "../helpers.h"
 
+enum class ThrowMode { Never, OnAssign, OnNthCopy };
+
+struct ThrowState {
+  ThrowMode mode         = ThrowMode::Never;
+  int copies_until_throw = 0; // Only used with ThrowMode::OnNthCopy.
+};
+
 struct ThrowingAssign {
-  static inline bool throw_on_assign = false;
+  static constexpr ThrowMode throw_mode = ThrowMode::OnAssign;
 
   int value;
+  ThrowState* state;
 
-  ThrowingAssign(int v) : value(v) {}
+  ThrowingAssign(int v, ThrowState& s) : value(v), state(&s) {}
   ThrowingAssign(const ThrowingAssign&)     = default;
   ThrowingAssign(ThrowingAssign&&) noexcept = default;
   ThrowingAssign& operator=(const ThrowingAssign& other) {
-    if (throw_on_assign)
+    if (state->mode == ThrowMode::OnAssign)
       throw 42;
     value = other.value;
     return *this;
   }
   ThrowingAssign& operator=(ThrowingAssign&& other) {
-    if (throw_on_assign)
+    if (state->mode == ThrowMode::OnAssign)
       throw 42;
     value = other.value;
     return *this;
   }
 
-  friend bool operator==(const ThrowingAssign&, const ThrowingAssign&)  = default;
-  friend auto operator<=>(const ThrowingAssign&, const ThrowingAssign&) = default;
+  friend bool operator==(const ThrowingAssign& x, const ThrowingAssign& y) { return x.value == y.value; }
+  friend auto operator<=>(const ThrowingAssign& x, const ThrowingAssign& y) { return x.value <=> y.value; }
 };
 
 struct ThrowingCopy {
-  static inline int copies_until_throw = -1; // -1: never throw
+  static constexpr ThrowMode throw_mode = ThrowMode::OnNthCopy;
 
   int value;
+  ThrowState* state;
 
-  ThrowingCopy(int v) : value(v) {}
-  ThrowingCopy(const ThrowingCopy& other) : value(other.value) {
-    if (copies_until_throw >= 0 && copies_until_throw-- == 0)
+  ThrowingCopy(int v, ThrowState& s) : value(v), state(&s) {}
+  ThrowingCopy(const ThrowingCopy& other) : value(other.value), state(other.state) {
+    if (state->mode == ThrowMode::OnNthCopy && state->copies_until_throw-- == 0)
       throw 42;
   }
   ThrowingCopy(ThrowingCopy&& other) : ThrowingCopy(other) {}
   ThrowingCopy& operator=(const ThrowingCopy&) noexcept = default;
   ThrowingCopy& operator=(ThrowingCopy&&) noexcept      = default;
 
-  friend bool operator==(const ThrowingCopy&, const ThrowingCopy&)  = default;
-  friend auto operator<=>(const ThrowingCopy&, const ThrowingCopy&) = default;
+  friend bool operator==(const ThrowingCopy& x, const ThrowingCopy& y) { return x.value == y.value; }
+  friend auto operator<=>(const ThrowingCopy& x, const ThrowingCopy& y) { return x.value <=> y.value; }
 };
 
-// n >= 0: assignments throw, and the n-th copy (counting from 0) throws. n < 0: nothing throws.
-void set_throwing(int n) {
-  ThrowingAssign::throw_on_assign  = n >= 0;
-  ThrowingCopy::copies_until_throw = n;
+template <class T>
+T make_element(int v, ThrowState& state) {
+  if constexpr (std::is_same_v<T, int>)
+    return v;
+  else
+    return T(v, state);
 }
 
 template <class Container>
-Container make_container(std::initializer_list<int> il) {
+Container make_container(std::initializer_list<int> il, ThrowState& state) {
+  using T = typename Container::value_type;
   Container c;
   for (int v : il)
-    c.emplace_back(v);
+    c.push_back(make_element<T>(v, state));
   if constexpr (requires { c.reserve(8); }) {
     // ThrowingAssign: spare capacity makes vector insert in place.
     // ThrowingCopy: no spare capacity makes vector reallocate.
-    if constexpr (std::is_same_v<typename Container::value_type, ThrowingCopy>)
+    if constexpr (std::is_same_v<T, ThrowingCopy>)
       c.shrink_to_fit();
     else
       c.reserve(8);
@@ -94,31 +107,46 @@ Container make_container(std::initializer_list<int> il) {
 
 template <class KeyContainer, class ValueContainer, class F>
 void test_one(F insert_function) {
-  using Key   = typename KeyContainer::value_type;
-  using Value = typename ValueContainer::value_type;
-  using M     = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
+  using Key      = typename KeyContainer::value_type;
+  using Value    = typename ValueContainer::value_type;
+  using M        = std::flat_map<Key, Value, std::less<Key>, KeyContainer, ValueContainer>;
+  using Throwing = std::conditional_t<std::is_same_v<Key, int>, Value, Key>;
 
+  // For ThrowingCopy, throw on each of the first few copies in turn.
   for (int n = 0; n < 6; ++n) {
-    M m(std::sorted_unique, make_container<KeyContainer>({1, 3, 5}), make_container<ValueContainer>({10, 30, 50}));
-    set_throwing(n);
+    ThrowState state;
+    M m(std::sorted_unique,
+        make_container<KeyContainer>({1, 3, 5}, state),
+        make_container<ValueContainer>({10, 30, 50}, state));
+    Key key     = make_element<Key>(2, state);
+    Value value = make_element<Value>(20, state);
+
+    state.mode               = Throwing::throw_mode;
+    state.copies_until_throw = n;
     try {
-      insert_function(m);
+      insert_function(m, key, value);
     } catch (int) {
     }
-    set_throwing(-1);
+    state.mode = ThrowMode::Never;
     check_invariant(m);
   }
 }
 
 template <class KeyContainer, class ValueContainer>
 void test() {
-  test_one<KeyContainer, ValueContainer>([](auto& m) { m.emplace(2, 20); });
-  test_one<KeyContainer, ValueContainer>([](auto& m) { m.emplace_hint(m.begin() + 1, 2, 20); });
-  test_one<KeyContainer, ValueContainer>([](auto& m) { m.try_emplace(2, 20); });
-  test_one<KeyContainer, ValueContainer>([](auto& m) { m.try_emplace(m.begin() + 1, 2, 20); });
-  test_one<KeyContainer, ValueContainer>([](auto& m) { m.insert({2, 20}); });
-  test_one<KeyContainer, ValueContainer>([](auto& m) { m.insert(m.begin() + 1, {2, 20}); });
-  test_one<KeyContainer, ValueContainer>([](auto& m) { m.insert_or_assign(2, 20); });
+  test_one<KeyContainer, ValueContainer>([](auto& m, const auto& k, const auto& v) { m.emplace(k, v); });
+  test_one<KeyContainer, ValueContainer>([](auto& m, const auto& k, const auto& v) {
+    m.emplace_hint(m.begin() + 1, k, v);
+  });
+  test_one<KeyContainer, ValueContainer>([](auto& m, const auto& k, const auto& v) { m.try_emplace(k, v); });
+  test_one<KeyContainer, ValueContainer>([](auto& m, const auto& k, const auto& v) {
+    m.try_emplace(m.begin() + 1, k, v);
+  });
+  test_one<KeyContainer, ValueContainer>([](auto& m, const auto& k, const auto& v) { m.insert({k, v}); });
+  test_one<KeyContainer, ValueContainer>([](auto& m, const auto& k, const auto& v) {
+    m.insert(m.begin() + 1, {k, v});
+  });
+  test_one<KeyContainer, ValueContainer>([](auto& m, const auto& k, const auto& v) { m.insert_or_assign(k, v); });
 }
 
 int main(int, char**) {
diff --git a/libcxx/test/std/containers/container.adaptors/flat.set/flat.set.modifiers/emplace_throwing_element.pass.cpp b/libcxx/test/std/containers/container.adaptors/flat.set/flat.set.modifiers/emplace_throwing_element.pass.cpp
index 99ec437d5a173b..d78ea8a911dada 100644
--- a/libcxx/test/std/containers/container.adaptors/flat.set/flat.set.modifiers/emplace_throwing_element.pass.cpp
+++ b/libcxx/test/std/containers/container.adaptors/flat.set/flat.set.modifiers/emplace_throwing_element.pass.cpp
@@ -17,6 +17,7 @@
 // element type is Cpp17CopyInsertable.
 
 #include <cassert>
+#include <compare>
 #include <deque>
 #include <flat_set>
 #include <functional>
@@ -26,58 +27,58 @@
 #include "../helpers.h"
 
 struct ThrowingAssign {
-  static inline bool throw_on_assign = false;
-
   int value;
+  bool* throw_on_assign;
 
-  ThrowingAssign(int v) : value(v) {}
+  ThrowingAssign(int v, bool& t) : value(v), throw_on_assign(&t) {}
   ThrowingAssign(const ThrowingAssign&)     = default;
   ThrowingAssign(ThrowingAssign&&) noexcept = default;
   ThrowingAssign& operator=(const ThrowingAssign& other) {
-    if (throw_on_assign)
+    if (*throw_on_assign)
       throw 42;
     value = other.value;
     return *this;
   }
   ThrowingAssign& operator=(ThrowingAssign&& other) {
-    if (throw_on_assign)
+    if (*throw_on_assign)
       throw 42;
     value = other.value;
     return *this;
   }
 
-  friend bool operator==(const ThrowingAssign&, const ThrowingAssign&)  = default;
-  friend auto operator<=>(const ThrowingAssign&, const ThrowingAssign&) = default;
+  friend bool operator==(const ThrowingAssign& x, const ThrowingAssign& y) { return x.value == y.value; }
+  friend auto operator<=>(const ThrowingAssign& x, const ThrowingAssign& y) { return x.value <=> y.value; }
 };
 
 template <class KeyContainer, class F>
 void test_one(F insert_function) {
   using M = std::flat_set<ThrowingAssign, std::less<ThrowingAssign>, KeyContainer>;
 
+  bool throw_on_assign = false;
   KeyContainer keys;
   // Spare capacity makes vector insert in place instead of reallocating.
   if constexpr (requires { keys.reserve(8); })
     keys.reserve(8);
   for (int v : {1, 3, 5})
-    keys.emplace_back(v);
+    keys.emplace_back(v, throw_on_assign);
   M m(std::sorted_unique, std::move(keys));
 
-  ThrowingAssign::throw_on_assign = true;
+  throw_on_assign = true;
   try {
-    insert_function(m);
+    insert_function(m, throw_on_assign);
     assert(false);
   } catch (int) {
   }
-  ThrowingAssign::throw_on_assign = false;
+  throw_on_assign = false;
   check_invariant(m);
 }
 
 template <class KeyContainer>
 void test() {
-  test_one<KeyContainer>([](auto& m) { m.emplace(2); });
-  test_one<KeyContainer>([](auto& m) { m.emplace_hint(m.begin() + 1, 2); });
-  test_one<KeyContainer>([](auto& m) { m.insert(ThrowingAssign(2)); });
-  test_one<KeyContainer>([](auto& m) { m.insert(m.begin() + 1, ThrowingAssign(2)); });
+  test_one<KeyContainer>([](auto& m, bool& t) { m.emplace(2, t); });
+  test_one<KeyContainer>([](auto& m, bool& t) { m.emplace_hint(m.begin() + 1, 2, t); });
+  test_one<KeyContainer>([](auto& m, bool& t) { m.insert(ThrowingAssign(2, t)); });
+  test_one<KeyContainer>([](auto& m, bool& t) { m.insert(m.begin() + 1, ThrowingAssign(2, t)); });
 }
 
 int main(int, char**) {



More information about the libcxx-commits mailing list