Ok, here is the new patch, only fixing the std::erase_if behavior for
the _GLIBCXX_DEBUG.
I'll check for the std::inplace_vector one, I remember that something
made me implement it this way. I'll replied on the
__gnu_debug::inplace_vector mail thread.
Ok to commit ?
François
On 11/17/25 21:16, Jonathan Wakely wrote:
On Mon, 17 Nov 2025 at 19:08, François Dumont <[email protected]> wrote:
libstdc++: [_GLIBCXX_DEBUG] Fix std::erase_if for std::vector behavior
When using directly __gnu_debug::vector the std::erase_if is called
with a
reference to the std::vector base class and so is missing the
invalidation
of the iterators that might result from the erase.
When you first described this problem, I thought your explanation was
correct. But now I'm looking at the specification of std::erase_if and
I think the existing code is correct, and this change would be wrong.
The standard is very clear about the implementation of std::erase_if,
it is precisely what we do: call remove_if and then call c.erase(it,
c.end()). That means it does not invalidate all iterators from the
first erasure, it only invalidates iterators to the elements removed
from the end.
I think this patch is wrong, and I think the erase_if in your
__gnu_debug::inplace_vector patch is also wrong.
To fix this provide a std::erase_if overload dedicated to
__gnu_debug::vector.
Doing so we can cleanup the implementation dedicated to std::vector
from any
_GLIBCXX_DEBUG consideration.
libstdc++-v3/ChangeLog:
* include/debug/vector (std::erase_if, std::erase): New
overloads for
__gnu_debug::vector instances.
* include/std/vector (std::erase_if, std::erase): Make
overloads specific
to normal std::vector implementation.
* testsuite/23_containers/vector/debug/erase.cc: New test case.
*
testsuite/23_containers/vector/debug/invalidation/erase.cc: New test case.
Tested under Linux x86_64,
ok to commit ?
François
diff --git a/libstdc++-v3/include/debug/vector
b/libstdc++-v3/include/debug/vector
index 1b3486b0dc0..43162db7168 100644
--- a/libstdc++-v3/include/debug/vector
+++ b/libstdc++-v3/include/debug/vector
@@ -1038,6 +1038,32 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
} // namespace __detail::__variant
#endif // C++17
+#ifdef __cpp_lib_erase_if // C++ >= 20 && HOSTED
+ template<typename _Tp, typename _Alloc, typename _Predicate>
+ constexpr typename __debug::vector<_Tp, _Alloc>::size_type
+ erase_if(__debug::vector<_Tp, _Alloc>& __cont, _Predicate __pred)
+ {
+ _GLIBCXX_STD_C::vector<_Tp, _Alloc>& __unsafe_cont = __cont;
+ const auto __osz = __cont.size();
+ const auto __end = __unsafe_cont.end();
+ auto __removed = std::__remove_if(__unsafe_cont.begin(), __end,
+ std::move(__pred));
+ if (__removed != __end)
+ {
+ __cont.erase(__niter_wrap(__cont.begin(), __removed),
+ __cont.end());
+ return __osz - __cont.size();
+ }
+
+ return 0;
+ }
+
+ template<typename _Tp, typename _Alloc, typename _Up = _Tp>
+ constexpr typename __debug::vector<_Tp, _Alloc>::size_type
+ erase(__debug::vector<_Tp, _Alloc>& __cont, const _Up& __value)
+ { return std::erase_if(__cont, __gnu_cxx::__ops::__equal_to(__value)); }
+#endif // __cpp_lib_erase_if
+
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace std
diff --git a/libstdc++-v3/include/std/vector b/libstdc++-v3/include/std/vector
index 920f852f794..375011fff69 100644
--- a/libstdc++-v3/include/std/vector
+++ b/libstdc++-v3/include/std/vector
@@ -112,19 +112,16 @@ namespace std _GLIBCXX_VISIBILITY(default)
_GLIBCXX_BEGIN_NAMESPACE_VERSION
template<typename _Tp, typename _Alloc, typename _Predicate>
- constexpr typename vector<_Tp, _Alloc>::size_type
- erase_if(vector<_Tp, _Alloc>& __cont, _Predicate __pred)
+ constexpr typename _GLIBCXX_STD_C::vector<_Tp, _Alloc>::size_type
+ erase_if(_GLIBCXX_STD_C::vector<_Tp, _Alloc>& __cont, _Predicate __pred)
{
- using namespace __gnu_cxx;
- _GLIBCXX_STD_C::vector<_Tp, _Alloc>& __ucont = __cont;
const auto __osz = __cont.size();
- const auto __end = __ucont.end();
- auto __removed = std::__remove_if(__ucont.begin(), __end,
+ const auto __end = __cont.end();
+ auto __removed = std::__remove_if(__cont.begin(), __end,
std::move(__pred));
if (__removed != __end)
{
- __cont.erase(__niter_wrap(__cont.begin(), __removed),
- __cont.end());
+ __cont.erase(__removed, __end);
return __osz - __cont.size();
}
@@ -133,8 +130,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
template<typename _Tp, typename _Alloc,
typename _Up _GLIBCXX26_DEF_VAL_T(_Tp)>
- constexpr typename vector<_Tp, _Alloc>::size_type
- erase(vector<_Tp, _Alloc>& __cont, const _Up& __value)
+ constexpr typename _GLIBCXX_STD_C::vector<_Tp, _Alloc>::size_type
+ erase(_GLIBCXX_STD_C::vector<_Tp, _Alloc>& __cont, const _Up& __value)
{ return std::erase_if(__cont, __gnu_cxx::__ops::__equal_to(__value)); }
_GLIBCXX_END_NAMESPACE_VERSION
diff --git a/libstdc++-v3/testsuite/23_containers/vector/debug/erase.cc
b/libstdc++-v3/testsuite/23_containers/vector/debug/erase.cc
new file mode 100644
index 00000000000..d2b412d5f27
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/vector/debug/erase.cc
@@ -0,0 +1,27 @@
+// { dg-do run { target c++20 } }
+// { dg-require-debug-mode "" }
+
+#include <vector>
+#include <testsuite_hooks.h>
+
+void test01()
+{
+ std::vector<int> v;
+
+ for (int i = 0; i != 10; ++i)
+ v.push_back(i);
+
+ auto before = v.begin() + 4;
+ auto last = v.end() - 1;
+
+ VERIFY( std::erase(v, 6) == 1 );
+
+ VERIFY(before._M_dereferenceable());
+ VERIFY(last._M_singular());
+}
+
+int main()
+{
+ test01();
+ return 0;
+}
diff --git
a/libstdc++-v3/testsuite/23_containers/vector/debug/invalidation/erase.cc
b/libstdc++-v3/testsuite/23_containers/vector/debug/invalidation/erase.cc
new file mode 100644
index 00000000000..38326a75773
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/vector/debug/invalidation/erase.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++20 } }
+
+#include <debug/vector>
+#include <testsuite_hooks.h>
+
+using __gnu_debug::vector;
+
+void test01()
+{
+ vector<int> v;
+
+ for (int i = 0; i != 10; ++i)
+ v.push_back(i);
+
+ auto before = v.begin() + 4;
+ auto last = v.end() -1;
+
+ VERIFY( std::erase(v, 6) == 1 );
+
+ VERIFY(before._M_dereferenceable());
+ VERIFY(last._M_singular());
+}
+
+int main()
+{
+ test01();
+ return 0;
+}