[libcxx-commits] [libcxx] [libc++][tests] Add missing strong exception guarantee test coverage in forward_list (PR #200666)

via libcxx-commits libcxx-commits at lists.llvm.org
Tue Jun 2 13:55:10 PDT 2026


https://github.com/ovatonne updated https://github.com/llvm/llvm-project/pull/200666

>From 2e4d1d8995b66185c7d27fc9c963ae66cf441aa2 Mon Sep 17 00:00:00 2001
From: Odilon Vatonne <ovatonne at gmail.com>
Date: Sun, 31 May 2026 17:07:39 +0200
Subject: [PATCH 1/3] Add missing strong exception guarantee test coverage in
 forward_list

---
 .../sequences/forwardlist/exception_safety.pass.cpp   | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp b/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
index a6579e144e6d3..a9fe399b6c18a 100644
--- a/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
+++ b/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
@@ -35,6 +35,7 @@
 //     void assign_range(R&& rg); // C++23
 //
 // void push_front(const value_type& v);
+// void push_front(value_type&& v);
 // template <class... Args> reference emplace_front(Args&&... args);  // reference in C++17
 // template<container-compatible-range<T> R>
 //    void prepend_range(R&& rg); // C++23
@@ -74,9 +75,17 @@ int main(int, char**) {
       c.push_front(*from);
     });
 
+    // void push_front(value_type&& v);
+    test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+      c.emplace_front(std::move(*from));
+    });
+
     // template <class... Args> reference emplace_front(Args&&... args);
     test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
-      c.push_front(*from);
+      c.emplace_front(*from);
+    });
+    test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+      c.emplace_front(std::move(*from));
     });
 
     // iterator insert_after(const_iterator p, const value_type& v);

>From 5bc73cecad00bc1a6466e9cc188cb2cd8f9fc5b6 Mon Sep 17 00:00:00 2001
From: Odilon Vatonne <ovatonne at gmail.com>
Date: Sun, 31 May 2026 17:17:16 +0200
Subject: [PATCH 2/3] Add missing throwing condition in forward_list exception
 safety tests

---
 .../std/containers/exception_safety_helpers.h | 132 +++++++++++-------
 .../forwardlist/exception_safety.pass.cpp     |  59 +++++---
 2 files changed, 125 insertions(+), 66 deletions(-)

diff --git a/libcxx/test/std/containers/exception_safety_helpers.h b/libcxx/test/std/containers/exception_safety_helpers.h
index 2d26735d9bb4e..6e463304cce4f 100644
--- a/libcxx/test/std/containers/exception_safety_helpers.h
+++ b/libcxx/test/std/containers/exception_safety_helpers.h
@@ -17,74 +17,91 @@
 #include "test_macros.h"
 
 #if !defined(TEST_HAS_NO_EXCEPTIONS)
-template <int N>
-struct ThrowingCopy {
+template <int NDefault, int NInplace, int NCopy, int NMove>
+struct ThrowingBase {
   static bool throwing_enabled;
+  static int default_constructed;
+  static int created_inplace;
   static int created_by_copying;
+  static int created_by_moving;
   static int destroyed;
   int x = 0; // Allows distinguishing between different instances.
 
-  ThrowingCopy() = default;
-  ThrowingCopy(int value) : x(value) {}
-  ~ThrowingCopy() { ++destroyed; }
+  ThrowingBase() {
+    ++default_constructed;
+    if (throwing_enabled && default_constructed == NDefault) {
+      throw -1;
+    }
+  }
+  ThrowingBase(int value) : x(value) {
+    ++created_inplace;
+    if (throwing_enabled && created_inplace == NInplace) {
+      throw -1;
+    }
+  }
+  ~ThrowingBase() { ++destroyed; }
 
-  ThrowingCopy(const ThrowingCopy& other) : x(other.x) {
+  ThrowingBase(const ThrowingBase& other) : x(other.x) {
     ++created_by_copying;
-    if (throwing_enabled && created_by_copying == N) {
+    if (throwing_enabled && created_by_copying == NCopy) {
+      throw -1;
+    }
+  }
+
+  ThrowingBase(ThrowingBase&& other) : x(other.x) {
+    ++created_by_moving;
+    if (throwing_enabled && created_by_moving == NMove) {
       throw -1;
     }
   }
 
   // Defined to silence GCC warnings. For test purposes, only copy construction is considered `created_by_copying`.
-  ThrowingCopy& operator=(const ThrowingCopy& other) {
+  ThrowingBase& operator=(const ThrowingBase& other) {
+    x = other.x;
+    return *this;
+  }
+  ThrowingBase& operator=(ThrowingBase&& other) {
     x = other.x;
     return *this;
   }
 
-  friend bool operator==(const ThrowingCopy& lhs, const ThrowingCopy& rhs) { return lhs.x == rhs.x; }
-  friend bool operator<(const ThrowingCopy& lhs, const ThrowingCopy& rhs) { return lhs.x < rhs.x; }
+  friend bool operator==(const ThrowingBase& lhs, const ThrowingBase& rhs) { return lhs.x == rhs.x; }
+  friend bool operator<(const ThrowingBase& lhs, const ThrowingBase& rhs) { return lhs.x < rhs.x; }
 
-  static void reset() { created_by_copying = destroyed = 0; }
+  static void reset() {
+    default_constructed = created_inplace = created_by_copying = created_by_moving = destroyed = 0;
+  }
 };
 
-template <int N>
-bool ThrowingCopy<N>::throwing_enabled = true;
-template <int N>
-int ThrowingCopy<N>::created_by_copying = 0;
-template <int N>
-int ThrowingCopy<N>::destroyed = 0;
+template <int NDefault, int NInplace, int NCopy, int NMove>
+bool ThrowingBase<NDefault, NInplace, NCopy, NMove>::throwing_enabled = true;
+template <int NDefault, int NInplace, int NCopy, int NMove>
+int ThrowingBase<NDefault, NInplace, NCopy, NMove>::default_constructed = 0;
+template <int NDefault, int NInplace, int NCopy, int NMove>
+int ThrowingBase<NDefault, NInplace, NCopy, NMove>::created_inplace = 0;
+template <int NDefault, int NInplace, int NCopy, int NMove>
+int ThrowingBase<NDefault, NInplace, NCopy, NMove>::created_by_copying = 0;
+template <int NDefault, int NInplace, int NCopy, int NMove>
+int ThrowingBase<NDefault, NInplace, NCopy, NMove>::created_by_moving = 0;
+template <int NDefault, int NInplace, int NCopy, int NMove>
+int ThrowingBase<NDefault, NInplace, NCopy, NMove>::destroyed = 0;
+
+template <int NDefault, int NInplace, int NCopy, int NMove>
+struct std::hash<ThrowingBase<NDefault, NInplace, NCopy, NMove>> {
+  std::size_t operator()(const ThrowingBase<NDefault, NInplace, NCopy, NMove>& value) const { return value.x; }
+};
 
 template <int N>
-struct ThrowingDefault {
-  static bool throwing_enabled;
-  static int default_constructed;
-  static int destroyed;
-  int x = 0;
-
-  ThrowingDefault() {
-    ++default_constructed;
-    if (throwing_enabled && default_constructed == N) {
-      throw -1;
-    }
-  }
-
-  ThrowingDefault(int value) : x(value) {}
-  ThrowingDefault(const ThrowingDefault& other) = default;
-  friend bool operator==(const ThrowingDefault& lhs, const ThrowingDefault& rhs) { return lhs.x == rhs.x; }
-  friend bool operator<(const ThrowingDefault& lhs, const ThrowingDefault& rhs) { return lhs.x < rhs.x; }
-
-  static void reset() { default_constructed = destroyed = 0; }
-};
+using ThrowingDefault = ThrowingBase<N, 0, 0, 0>;
 
 template <int N>
-bool ThrowingDefault<N>::throwing_enabled = true;
+using ThrowingInplace = ThrowingBase<0, N, 0, 0>;
+
 template <int N>
-int ThrowingDefault<N>::default_constructed = 0;
+using ThrowingCopy = ThrowingBase<0, 0, N, 0>;
 
 template <int N>
-struct std::hash<ThrowingCopy<N>> {
-  std::size_t operator()(const ThrowingCopy<N>& value) const { return value.x; }
-};
+using ThrowingMove = ThrowingBase<0, 0, 0, N>;
 
 template <int ThrowOn, int Size, class Func>
 void test_exception_safety_throwing_copy(Func&& func) {
@@ -123,9 +140,9 @@ void test_exception_safety_throwing_copy_container(Func&& func) {
   }
 }
 
-template <int ThrowOn, int Size, class Func>
-void test_strong_exception_safety_throwing_copy(Func&& func) {
-  using T             = ThrowingCopy<ThrowOn>;
+template <int ThrowOnDefault, int ThrowOnInplace, int ThrowOnCopy, int ThrowOnMove, int Size, class Func>
+void test_strong_exception_safety(Func&& func) {
+  using T             = ThrowingBase<ThrowOnDefault, ThrowOnInplace, ThrowOnCopy, ThrowOnMove>;
   T::throwing_enabled = false;
 
   std::forward_list<T> c0(Size);
@@ -140,12 +157,31 @@ void test_strong_exception_safety_throwing_copy(Func&& func) {
     assert(false); // The function call above should throw.
 
   } catch (int) {
-    assert(T::created_by_copying == ThrowOn);
-    assert(T::destroyed == ThrowOn - 1); // No destructor call for the partially-constructed element.
-    assert(c == c0);                     // Strong exception guarantee
+    assert(T::default_constructed == ThrowOnDefault);
+    assert(T::created_inplace == ThrowOnInplace);
+    assert(T::created_by_copying == ThrowOnCopy);
+    assert(T::created_by_moving == ThrowOnMove);
+    assert(T::destroyed == ThrowOnDefault + ThrowOnInplace + ThrowOnCopy + ThrowOnMove -
+                               1); // No destructor call for the partially-constructed element.
+    assert(c == c0);               // Strong exception guarantee
   }
 }
 
+template <int ThrowOn, int Size, class Func>
+void test_strong_exception_safety_throwing_inplace(Func&& func) {
+  test_strong_exception_safety<0, ThrowOn, 0, 0, Size, Func>(std::forward<Func>(func));
+}
+
+template <int ThrowOn, int Size, class Func>
+void test_strong_exception_safety_throwing_copy(Func&& func) {
+  test_strong_exception_safety<0, 0, ThrowOn, 0, Size, Func>(std::forward<Func>(func));
+}
+
+template <int ThrowOn, int Size, class Func>
+void test_strong_exception_safety_throwing_move(Func&& func) {
+  test_strong_exception_safety<0, 0, 0, ThrowOn, Size, Func>(std::forward<Func>(func));
+}
+
 #endif // !defined(TEST_HAS_NO_EXCEPTIONS)
 
 #endif // SUPPORT_EXCEPTION_SAFETY_HELPERS_H
diff --git a/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp b/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
index a9fe399b6c18a..4d6ec2731b719 100644
--- a/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
+++ b/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
@@ -10,11 +10,6 @@
 
 // UNSUPPORTED: c++03, no-exceptions
 
-// TODO:
-// - throwing upon moving;
-// - initializer lists;
-// - throwing when constructing the element in place.
-
 // forward_list(size_type n, const value_type& v);
 // forward_list(size_type n, const value_type& v, const allocator_type& a);
 // template <class InputIterator>
@@ -75,36 +70,64 @@ int main(int, char**) {
       c.push_front(*from);
     });
 
-    // void push_front(value_type&& v);
-    test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
-      c.emplace_front(std::move(*from));
-    });
-
     // template <class... Args> reference emplace_front(Args&&... args);
     test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
       c.emplace_front(*from);
     });
-    test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
-      c.emplace_front(std::move(*from));
-    });
 
     // iterator insert_after(const_iterator p, const value_type& v);
     test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
       c.insert_after(c.before_begin(), *from);
     });
 
-    // iterator insert_after(const_iterator p, value_type&& v);
+    // template <class... Args>
+    //     iterator emplace_after(const_iterator p, Args&&... args);
     test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+      c.emplace_after(c.before_begin(), *from);
+    });
+  }
+
+  {
+    constexpr int ThrowOn = 1;
+    constexpr int Size    = 1;
+    using T               = ThrowingMove<ThrowOn>;
+
+    // void push_front(value_type&& v);
+    test_strong_exception_safety_throwing_move<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+      c.push_front(std::move(*from));
+    });
+
+    // template <class... Args>
+    //     iterator emplace_after(const_iterator p, Args&&... args);
+    test_strong_exception_safety_throwing_move<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+      c.emplace_after(c.before_begin(), std::move(*from));
+    });
+
+    // template <class... Args> reference emplace_front(Args&&... args);
+    test_strong_exception_safety_throwing_move<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+      c.emplace_front(std::move(*from));
+    });
+
+    // iterator insert_after(const_iterator p, value_type&& v);
+    test_strong_exception_safety_throwing_move<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
       c.insert_after(c.before_begin(), std::move(*from));
     });
+  }
+
+  {
+    constexpr int ThrowOn = 1;
+    constexpr int Size    = 1;
+    using T               = ThrowingInplace<ThrowOn>;
 
     // template <class... Args>
     //     iterator emplace_after(const_iterator p, Args&&... args);
-    test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
-      c.emplace_after(c.before_begin(), *from);
+    test_strong_exception_safety_throwing_inplace<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+      c.emplace_after(c.before_begin(), from->x);
     });
-    test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
-      c.emplace_after(c.before_begin(), std::move(*from));
+
+    // template <class... Args> reference emplace_front(Args&&... args);
+    test_strong_exception_safety_throwing_inplace<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
+      c.emplace_front(from->x);
     });
   }
 

>From c6f6a55f244191180c8032a486cf4cd1a2307782 Mon Sep 17 00:00:00 2001
From: Odilon Vatonne <ovatonne at gmail.com>
Date: Tue, 2 Jun 2026 22:54:52 +0200
Subject: [PATCH 3/3] Add missing throwing condition in forward_list for
 initializer_list overloads

---
 .../forwardlist/exception_safety.pass.cpp     | 33 +++++++++++++++++--
 1 file changed, 31 insertions(+), 2 deletions(-)

diff --git a/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp b/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
index 4d6ec2731b719..a345665634276 100644
--- a/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
+++ b/libcxx/test/std/containers/sequences/forwardlist/exception_safety.pass.cpp
@@ -18,14 +18,18 @@
 //     forward_list(InputIterator first, InputIterator last, const allocator_type& a);
 // forward_list(const forward_list& x);
 // forward_list(const forward_list& x, const allocator_type& a);
+// forward_list(initializer_list<value_type> il);
+// forward_list(initializer_list<value_type> il, const allocator_type& a);
 // template<container-compatible-range<T> R>
 //     forward_list(from_range_t, R&& rg, const Allocator& = Allocator()); // C++23
 //
 // forward_list& operator=(const forward_list& x);
+// forward_list& operator=(initializer_list<value_type> il);
 //
 // template <class InputIterator>
 //     void assign(InputIterator first, InputIterator last);
 // void assign(size_type n, const value_type& v);
+// void assign(initializer_list<value_type> il);
 // template<container-compatible-range<T> R>
 //     void assign_range(R&& rg); // C++23
 //
@@ -138,6 +142,8 @@ int main(int, char**) {
     using C               = std::forward_list<T>;
     using Alloc           = std::allocator<T>;
 
+    std::initializer_list<T> il{1, 2, 3, 4, 5};
+
     // forward_list(size_type n, const value_type& v);
     test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T*) {
       std::forward_list<T> c(Size, *from);
@@ -192,12 +198,30 @@ int main(int, char**) {
       (void)c;
     });
 
+    // forward_list(initializer_list<value_type> il);
+    test_exception_safety_throwing_copy<ThrowOn, Size>([&il](T*, T*) {
+      std::forward_list<T> c(il);
+      (void)c;
+    });
+
+    // forward_list(initializer_list<value_type> il, const allocator_type& a);
+    test_exception_safety_throwing_copy<ThrowOn, Size>([&il](T*, T*) {
+      std::forward_list<T> c(il, Alloc());
+      (void)c;
+    });
+
     // forward_list& operator=(const forward_list& x);
     test_exception_safety_throwing_copy_container<C, ThrowOn, Size>([](C&& in) {
       std::forward_list<T> c;
       c = in;
     });
 
+    // forward_list& operator=(initializer_list<value_type> il);
+    test_exception_safety_throwing_copy<ThrowOn, Size>([&il](T*, T*) {
+      std::forward_list<T> c;
+      c = il;
+    });
+
     // template <class InputIterator>
     //     void assign(InputIterator first, InputIterator last);
     test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T* to) {
@@ -205,6 +229,12 @@ int main(int, char**) {
       c.assign(from, to);
     });
 
+    // void assign(initializer_list<value_type> il);
+    test_exception_safety_throwing_copy<ThrowOn, Size>([&il](T*, T*) {
+      std::forward_list<T> c;
+      c.assign(il);
+    });
+
 #if TEST_STD_VER >= 23
     // template<container-compatible-range<T> R>
     //     void assign_range(R&& rg); // C++23
@@ -241,8 +271,7 @@ int main(int, char**) {
     });
 
     // iterator insert_after(const_iterator p, initializer_list<value_type> il);
-    std::initializer_list<T> il{1, 2, 3, 4, 5};
-    test_strong_exception_safety_throwing_copy<ThrowOn, Size>([&](std::forward_list<T>& c, T*, T*) {
+    test_strong_exception_safety_throwing_copy<ThrowOn, Size>([&il](std::forward_list<T>& c, T*, T*) {
       c.insert_after(c.before_begin(), il);
     });
 



More information about the libcxx-commits mailing list