[libc++] Ensure strong exception guarantee for forward_list::resize (#131025)
The current implementation of `forward_list::resize` does not meet the
strong exception safety guarantee required by [forward.list.modifiers]:
If an exception is thrown by any of these member functions there is no
effect on the container.
This patch refactors `resize()` to provide strong exception safety and
introduces additional tests to validate the strong exception guarantees
for other `forward_list` modifiers.
Fixes #118366.
This commit is contained in:
@@ -107,7 +107,7 @@
|
||||
"`LWG4153 <https://wg21.link/LWG4153>`__","Fix extra ""-1"" for ``philox_engine::max()``","2024-11 (Wrocław)","","",""
|
||||
"`LWG4154 <https://wg21.link/LWG4154>`__","The Mandates for ``std::packaged_task``'s constructor from a callable entity should consider decaying","2024-11 (Wrocław)","","",""
|
||||
"`LWG4157 <https://wg21.link/LWG4157>`__","The resolution of LWG3465 was damaged by P2167R3","2024-11 (Wrocław)","","20",""
|
||||
"`LWG4164 <https://wg21.link/LWG4164>`__","Missing guarantees for ``forward_list`` modifiers","2024-11 (Wrocław)","","",""
|
||||
"`LWG4164 <https://wg21.link/LWG4164>`__","Missing guarantees for ``forward_list`` modifiers","2024-11 (Wrocław)","|Complete|","21",""
|
||||
"`LWG4169 <https://wg21.link/LWG4169>`__","``std::atomic<T>``'s default constructor should be constrained","2024-11 (Wrocław)","","",""
|
||||
"`LWG4170 <https://wg21.link/LWG4170>`__","``contiguous_iterator`` should require ``to_address(I{})``","2024-11 (Wrocław)","","",""
|
||||
"","","","","",""
|
||||
|
||||
|
@@ -806,7 +806,9 @@ public:
|
||||
}
|
||||
# endif // _LIBCPP_CXX03_LANG
|
||||
_LIBCPP_HIDE_FROM_ABI iterator insert_after(const_iterator __p, const value_type& __v);
|
||||
_LIBCPP_HIDE_FROM_ABI iterator insert_after(const_iterator __p, size_type __n, const value_type& __v);
|
||||
_LIBCPP_HIDE_FROM_ABI iterator insert_after(const_iterator __p, size_type __n, const value_type& __v) {
|
||||
return __insert_after(__p, __n, __v);
|
||||
}
|
||||
template <class _InputIterator, __enable_if_t<__has_input_iterator_category<_InputIterator>::value, int> = 0>
|
||||
_LIBCPP_HIDE_FROM_ABI iterator insert_after(const_iterator __p, _InputIterator __f, _InputIterator __l);
|
||||
|
||||
@@ -876,6 +878,9 @@ private:
|
||||
template <class _Iter, class _Sent>
|
||||
_LIBCPP_HIDE_FROM_ABI void __assign_with_sentinel(_Iter __f, _Sent __l);
|
||||
|
||||
template <class... _Args>
|
||||
_LIBCPP_HIDE_FROM_ABI iterator __insert_after(const_iterator __p, size_type __n, _Args&&... __args);
|
||||
|
||||
template <class _Compare>
|
||||
static _LIBCPP_HIDE_FROM_ABI __node_pointer __merge(__node_pointer __f1, __node_pointer __f2, _Compare& __comp);
|
||||
|
||||
@@ -1129,17 +1134,18 @@ forward_list<_Tp, _Alloc>::insert_after(const_iterator __p, const value_type& __
|
||||
}
|
||||
|
||||
template <class _Tp, class _Alloc>
|
||||
template <class... _Args>
|
||||
typename forward_list<_Tp, _Alloc>::iterator
|
||||
forward_list<_Tp, _Alloc>::insert_after(const_iterator __p, size_type __n, const value_type& __v) {
|
||||
forward_list<_Tp, _Alloc>::__insert_after(const_iterator __p, size_type __n, _Args&&... __args) {
|
||||
__begin_node_pointer __r = __p.__get_begin();
|
||||
if (__n > 0) {
|
||||
__node_pointer __first = this->__create_node(/* next = */ nullptr, __v);
|
||||
__node_pointer __first = this->__create_node(/* next = */ nullptr, std::forward<_Args>(__args)...);
|
||||
__node_pointer __last = __first;
|
||||
# if _LIBCPP_HAS_EXCEPTIONS
|
||||
try {
|
||||
# endif // _LIBCPP_HAS_EXCEPTIONS
|
||||
for (--__n; __n != 0; --__n, __last = __last->__next_) {
|
||||
__last->__next_ = this->__create_node(/* next = */ nullptr, __v);
|
||||
__last->__next_ = this->__create_node(/* next = */ nullptr, std::forward<_Args>(__args)...);
|
||||
}
|
||||
# if _LIBCPP_HAS_EXCEPTIONS
|
||||
} catch (...) {
|
||||
@@ -1239,14 +1245,8 @@ void forward_list<_Tp, _Alloc>::resize(size_type __n) {
|
||||
;
|
||||
if (__i != __e)
|
||||
erase_after(__p, __e);
|
||||
else {
|
||||
__n -= __sz;
|
||||
if (__n > 0) {
|
||||
for (__begin_node_pointer __ptr = __p.__get_begin(); __n > 0; --__n, __ptr = __ptr->__next_as_begin()) {
|
||||
__ptr->__next_ = this->__create_node(/* next = */ nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
__insert_after(__p, __n - __sz);
|
||||
}
|
||||
|
||||
template <class _Tp, class _Alloc>
|
||||
@@ -1259,14 +1259,8 @@ void forward_list<_Tp, _Alloc>::resize(size_type __n, const value_type& __v) {
|
||||
;
|
||||
if (__i != __e)
|
||||
erase_after(__p, __e);
|
||||
else {
|
||||
__n -= __sz;
|
||||
if (__n > 0) {
|
||||
for (__begin_node_pointer __ptr = __p.__get_begin(); __n > 0; --__n, __ptr = __ptr->__next_as_begin()) {
|
||||
__ptr->__next_ = this->__create_node(/* next = */ nullptr, __v);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
__insert_after(__p, __n - __sz, __v);
|
||||
}
|
||||
|
||||
template <class _Tp, class _Alloc>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <forward_list>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include "test_macros.h"
|
||||
@@ -53,6 +54,33 @@ int ThrowingCopy<N>::created_by_copying = 0;
|
||||
template <int N>
|
||||
int ThrowingCopy<N>::destroyed = 0;
|
||||
|
||||
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; }
|
||||
};
|
||||
|
||||
template <int N>
|
||||
bool ThrowingDefault<N>::throwing_enabled = true;
|
||||
template <int N>
|
||||
int ThrowingDefault<N>::default_constructed = 0;
|
||||
|
||||
template <int N>
|
||||
struct std::hash<ThrowingCopy<N>> {
|
||||
std::size_t operator()(const ThrowingCopy<N>& value) const { return value.x; }
|
||||
@@ -95,6 +123,29 @@ 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>;
|
||||
T::throwing_enabled = false;
|
||||
|
||||
std::forward_list<T> c0(Size);
|
||||
for (int i = 0; i < Size; ++i)
|
||||
c0.emplace_front(i);
|
||||
std::forward_list<T> c = c0;
|
||||
T in[Size];
|
||||
T::reset();
|
||||
T::throwing_enabled = true;
|
||||
try {
|
||||
func(c, in, in + Size);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !defined(TEST_HAS_NO_EXCEPTIONS)
|
||||
|
||||
#endif // SUPPORT_EXCEPTION_SAFETY_HELPERS_H
|
||||
|
||||
@@ -35,18 +35,23 @@
|
||||
// void assign_range(R&& rg); // C++23
|
||||
//
|
||||
// void push_front(const value_type& v);
|
||||
// template<container-compatible-range<T> R>
|
||||
// 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
|
||||
//
|
||||
// iterator insert_after(const_iterator p, const value_type& v);
|
||||
// iterator insert_after(const_iterator p, value_type&& v);
|
||||
// iterator insert_after(const_iterator p, size_type n, const value_type& v);
|
||||
// iterator insert_after(const_iterator p, initializer_list<value_type> il);
|
||||
// template <class InputIterator>
|
||||
// iterator insert_after(const_iterator p,
|
||||
// InputIterator first, InputIterator last);
|
||||
// template<container-compatible-range<T> R>
|
||||
// template<container-compatible-range<T> R>
|
||||
// iterator insert_range_after(const_iterator position, R&& rg); // C++23
|
||||
//
|
||||
// template <class... Args>
|
||||
// iterator emplace_after(const_iterator p, Args&&... args);
|
||||
// void resize(size_type n, const value_type& v);
|
||||
// void resize(size_type n);
|
||||
|
||||
#include <forward_list>
|
||||
|
||||
@@ -65,16 +70,33 @@ int main(int, char**) {
|
||||
using T = ThrowingCopy<ThrowOn>;
|
||||
|
||||
// void push_front(const value_type& v);
|
||||
test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T*) {
|
||||
std::forward_list<T> c;
|
||||
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
|
||||
c.push_front(*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);
|
||||
});
|
||||
|
||||
// iterator insert_after(const_iterator p, const value_type& v);
|
||||
test_exception_safety_throwing_copy</*ThrowOn=*/1, Size>([](T* from, T*) {
|
||||
std::forward_list<T> c;
|
||||
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);
|
||||
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
|
||||
c.insert_after(c.before_begin(), std::move(*from));
|
||||
});
|
||||
|
||||
// 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_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
|
||||
c.emplace_after(c.before_begin(), std::move(*from));
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
@@ -169,40 +191,53 @@ int main(int, char**) {
|
||||
#if TEST_STD_VER >= 23
|
||||
// template<container-compatible-range<T> R>
|
||||
// void prepend_range(R&& rg); // C++23
|
||||
test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T* to) {
|
||||
std::forward_list<T> c;
|
||||
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T* to) {
|
||||
c.prepend_range(std::ranges::subrange(from, to));
|
||||
});
|
||||
#endif
|
||||
|
||||
// iterator insert_after(const_iterator p, size_type n, const value_type& v);
|
||||
test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T*) {
|
||||
std::forward_list<T> c;
|
||||
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
|
||||
c.insert_after(c.before_begin(), Size, *from);
|
||||
});
|
||||
|
||||
// template <class InputIterator>
|
||||
// iterator insert_after(const_iterator p,
|
||||
// InputIterator first, InputIterator last);
|
||||
test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T* to) {
|
||||
std::forward_list<T> c;
|
||||
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T* to) {
|
||||
c.insert_after(c.before_begin(), from, to);
|
||||
});
|
||||
|
||||
// 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*) {
|
||||
c.insert_after(c.before_begin(), il);
|
||||
});
|
||||
|
||||
#if TEST_STD_VER >= 23
|
||||
// template<container-compatible-range<T> R>
|
||||
// iterator insert_range_after(const_iterator position, R&& rg); // C++23
|
||||
test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T* to) {
|
||||
std::forward_list<T> c;
|
||||
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T* to) {
|
||||
c.insert_range_after(c.before_begin(), std::ranges::subrange(from, to));
|
||||
});
|
||||
#endif
|
||||
|
||||
// void resize(size_type n, const value_type& v);
|
||||
test_exception_safety_throwing_copy<ThrowOn, Size>([](T* from, T*) {
|
||||
std::forward_list<T> c;
|
||||
c.resize(Size, *from);
|
||||
test_strong_exception_safety_throwing_copy<ThrowOn, Size>([](std::forward_list<T>& c, T* from, T*) {
|
||||
c.resize(Size * 3, *from);
|
||||
});
|
||||
|
||||
{ // void resize(size_type n);
|
||||
using X = ThrowingDefault<ThrowOn>;
|
||||
std::forward_list<X> c0{X{1}, X{2}, X{3}};
|
||||
std::forward_list<X> c = c0;
|
||||
try {
|
||||
c.resize(3 * ThrowOn);
|
||||
assert(false);
|
||||
} catch (int) {
|
||||
assert(c == c0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user