On Tue, 07 Jul 2026 at 14:30 +0100, Jonathan Wakely wrote:
On Mon, 06 Jul 2026 at 18:40 +0200, François Dumont wrote:
    libstdc++: [_GLIBCXX_DEBUG] Enhance __gnu_debug::inplace_vector implementation

    Review invalidation of iterators to use a RAII approach. A guard type has been     implemented that is in charge of running an invalidation policy on destruction
    when not in a constant evaluation context.

Using RAII changes the semantics of these functions when the base
class operation throws an exception. In some cases that's fine.

For resize(n), if it throws then the size doesn't change, and the
_InvalidatePolicy does nothing. Great.

For append_range, if constructing a new element throws, the size
doesn't change, and the guard does nothing. Great.

But for emplace_back, if the new element throws an exception
from its constructor, then the standard says there are no effects. The
size doesn't change, and no iterators are invalidated. But with this
change, the pedantic guard will

    Invalidation of iterators on assign operation has been reviewed. All iterators     are being invalidated when using the _GLIBCXX_DEBUG_PEDANTIC mode. Otherwise     invalidate only iterators that were removed by the assignment operation.

    libstdc++-v3/ChangeLog:

            * include/debug/assertions.h (_GLIBCXX_DEBUG_STD_C): New.
            * include/debug/inplace_vector (__gnu_debug::_Inplace_vector_guard): New.
            (inplace_vector::_InvalidatePolicy): New.
            (inplace_vector::_InvalidateGuard): New.
            (inplace_vector::_InvalidateAfterNthPolicy): New.
            (inplace_vector::_InvalidateAfterNthGuard): New.
[_GLIBCXX_DEBUG_PEDANTIC](inplace_vector::_PedanticInvalidatePolicy): New.
            (inplace_vector::_AssignGuard): New.
            (inplace_vector): Adapt using latter guards.
            * testsuite/23_containers/inplace_vector/debug/initializer_list.cc: New test case.
            * 
testsuite/23_containers/inplace_vector/debug/initializer_list_pedantic.cc:
            New test case.

Tested under Linux x64_86 _GLIBCXX_DEBUG mode.

Ok to commit ?

François



diff --git a/libstdc++-v3/include/debug/assertions.h 
b/libstdc++-v3/include/debug/assertions.h
index c4993dc8596..768a7f05228 100644
--- a/libstdc++-v3/include/debug/assertions.h
+++ b/libstdc++-v3/include/debug/assertions.h
@@ -31,6 +31,12 @@

#include <bits/c++config.h>

+#ifdef _GLIBCXX_DEBUG
+# define _GLIBCXX_DEBUG_STD_C ::std::_GLIBCXX_STD_C
+#else
+# define _GLIBCXX_DEBUG_STD_C ::std
+#endif

I've wanted this many times over the years :-)
There are probably other places that we could use this (but not as
part of this patch).

#ifndef _GLIBCXX_DEBUG
// Verify that [_First, _Last) forms a non-empty iterator range.
# define __glibcxx_requires_non_empty_range(_First,_Last)       \
diff --git a/libstdc++-v3/include/debug/inplace_vector 
b/libstdc++-v3/include/debug/inplace_vector
index e579e8636f9..6ce4d006c90 100644
--- a/libstdc++-v3/include/debug/inplace_vector
+++ b/libstdc++-v3/include/debug/inplace_vector
@@ -46,6 +46,33 @@ namespace std _GLIBCXX_VISIBILITY(default) { namespace 
__debug {
#include <debug/safe_sequence.h>
#include <debug/safe_iterator.h>

+namespace __gnu_debug
+{
+  template<typename _Tp, size_t _Nm, typename _InvalidationPolicy>
+    struct _Inplace_vector_guard
+    {
+      using _SafeInplaceVector = std::__debug::inplace_vector<_Tp, _Nm>;
+      using _StdInplaceVector = _GLIBCXX_DEBUG_STD_C::inplace_vector<_Tp, _Nm>;
+      using difference_type = typename _StdInplaceVector::difference_type;
+
+      constexpr
+      _Inplace_vector_guard(_SafeInplaceVector& __vector,
+                           difference_type __offset = 0)
+      : _M_vector(__vector), _M_invalidation_policy(__vector, __offset) { }
+
+      constexpr
+      ~_Inplace_vector_guard()
+      {
+       if (!std::is_constant_evaluated())
+         _M_invalidation_policy(_M_vector);

This is C++26 code, so should be just:

         if ! consteval {
           _M_invalidation_policy(_M_vector);
         }

This is simpler to compile.

+      }
+
+    private:
+      _SafeInplaceVector& _M_vector;
+      _InvalidationPolicy _M_invalidation_policy;
+    };
+}
+
namespace std _GLIBCXX_VISIBILITY(default)
{
namespace __debug
@@ -80,6 +107,70 @@ namespace __debug
     using reverse_iterator       = std::reverse_iterator<iterator>;
     using const_reverse_iterator = std::reverse_iterator<const_iterator>;

+    private:
+      struct _InvalidatePolicy
+      {
+       size_type _M_size;
+       _Base_const_iterator _M_end;
+
+       constexpr
+       _InvalidatePolicy(const inplace_vector& __vector,
+                                 difference_type)
+       : _M_size(__vector.size()), _M_end(__vector._M_base().end()) { }
+
+       void
+       operator()(inplace_vector& __vector) const noexcept
+       {
+         const size_type __size = __vector.size();
+         if (__size < _M_size)
+           __vector._M_invalidate_after_nth(__size);
+         else if (__size > _M_size)
+           __vector._M_invalidate_if(_Equal(_M_end));
+       }
+      };
+
+      using _InvalidateGuard =
+       __gnu_debug::_Inplace_vector_guard<_Tp, _Nm, _InvalidatePolicy>;
+
+      struct _InvalidateAfterNthPolicy
+      {
+       size_type _M_size;
+       difference_type _M_offset;
+
+       constexpr
+       _InvalidateAfterNthPolicy(const inplace_vector& __vector,
+                                 difference_type __offset)
+       : _M_size(__vector.size()), _M_offset(__offset) { }
+
+       void
+       operator()(inplace_vector& __vector) const noexcept
+       {
+         if (__vector.size() != _M_size)
+           __vector._M_invalidate_after_nth(_M_offset);
+       }
+      };
+
+      using _InvalidateAfterNthGuard =
+       __gnu_debug::_Inplace_vector_guard<_Tp, _Nm, _InvalidateAfterNthPolicy>;
+
+#ifdef _GLIBCXX_DEBUG_PEDANTIC

Please add comments explaining how and why we're deviating from what
the standard says.

As I wrote just now in
https://gcc.gnu.org/pipermail/gcc-patches/2026-July/723049.html
I don't think we want to deviate from the standard anyway.

+      struct _PedanticInvalidatePolicy
+      {
+       constexpr
+       _PedanticInvalidatePolicy(const inplace_vector&, difference_type) { }
+
+       void
+       operator()(inplace_vector& __vector) const noexcept
+       { __vector._M_invalidate_all(); }
+      };
+
+      using _AssignGuard =
+       __gnu_debug::_Inplace_vector_guard<_Tp, _Nm, _PedanticInvalidatePolicy>;
+#else
+      using _AssignGuard = _InvalidateGuard;
+#endif
+
+    public:
     constexpr
     inplace_vector() noexcept = default;

@@ -120,8 +211,8 @@ namespace __debug
     constexpr inplace_vector&
     operator=(initializer_list<_Tp> __il)
     {
+       _AssignGuard __guard(*this);
        _Base::operator=(__il);
-       this->_M_invalidate_all();
        return *this;
     }

@@ -129,65 +220,63 @@ namespace __debug
        constexpr void
        assign(_InputIterator __first, _InputIterator __last)
        {
+         if (std::is_constant_evaluated())

Use 'if consteval' here.

+           return _Base::assign(__gnu_debug::__unsafe(__first),
+                                __gnu_debug::__unsafe(__last));
+
          typename __gnu_debug::_Distance_traits<_InputIterator>::__type __dist;
          __glibcxx_check_valid_range2(__first, __last, __dist);

-         const auto __size = size();
-         const auto __end = _Base::end();
+         _AssignGuard __guard(*this);
          if (__dist.second >= __gnu_debug::__dp_sign)
            _Base::assign(__gnu_debug::__unsafe(__first),
                          __gnu_debug::__unsafe(__last));
          else
            _Base::assign(__first, __last);
-
-         if (size() < __size)
-           _M_invalidate_after_nth(size());
-         else if (size() > __size)
-           this->_M_invalidate_if(_Equal(__end));
        }

     template<__detail::__container_compatible_range<_Tp> _Rg>
        constexpr void
        assign_range(_Rg&& __rg)
        {
+         _AssignGuard __guard(*this);
          _Base::assign_range(std::forward<_Rg>(__rg));
-         this->_M_invalidate_all();
        }

     constexpr void
     assign(size_type __n, const _Tp& __u)
     {
+       _AssignGuard __guard(*this);
        _Base::assign(__n, __u);
-       this->_M_invalidate_all();
     }

     constexpr void
     assign(initializer_list<_Tp> __il)
     {
+       _AssignGuard __guard(*this);
        _Base::assign(__il);
-       this->_M_invalidate_all();
     }

     // iterators
     [[nodiscard]]
     constexpr iterator
     begin() noexcept
-      { return { _Base::begin(), this }; }
+      { return iterator(_Base::begin(), this); }

     [[nodiscard]]
     constexpr const_iterator
     begin() const noexcept
-      { return { _Base::begin(), this }; }
+      { return const_iterator(_Base::begin(), this); }

     [[nodiscard]]
     constexpr iterator
     end() noexcept
-      { return { _Base::end(), this }; }
+      { return iterator(_Base::end(), this); }

     [[nodiscard]]
     constexpr const_iterator
     end() const noexcept
-      { return { _Base::end(), this }; }
+      { return const_iterator(_Base::end(), this); }

     [[nodiscard]]
     constexpr reverse_iterator
@@ -210,12 +299,12 @@ namespace __debug
     [[nodiscard]]
     constexpr const_iterator
     cbegin() const noexcept
-      { return { _Base::cbegin(), this }; }
+      { return const_iterator(_Base::cbegin(), this); }

     [[nodiscard]]
     constexpr const_iterator
     cend() const noexcept
-      { return { _Base::cend(), this }; }
+      { return const_iterator(_Base::cend(), this); }

     [[nodiscard]]
     constexpr const_reverse_iterator
@@ -233,15 +322,15 @@ namespace __debug
     constexpr void
     resize(size_type __n)
     {
+       _InvalidateGuard __guard(*this);
        _Base::resize(__n);
-       _M_invalidate_after_nth(__n);
     }

     constexpr void
     resize(size_type __n, const _Tp& __c)
     {
+       _InvalidateGuard __guard(*this);
        _Base::resize(__n, __c);
-       _M_invalidate_after_nth(__n);
     }

     using _Base::reserve;
@@ -304,10 +393,8 @@ namespace __debug
        constexpr _Tp&
        emplace_back(_Args&&... __args)
        {
-         const auto __end = _Base::cend();
-         _Tp& __res =  _Base::emplace_back(std::forward<_Args>(__args)...);
-         this->_M_invalidate_if(_Equal(__end));
-         return __res;
+         _InvalidateGuard __guard(*this);
+         return _Base::emplace_back(std::forward<_Args>(__args)...);
        }

     constexpr _Tp&
@@ -322,18 +409,15 @@ namespace __debug
        constexpr void
        append_range(_Rg&& __rg)
        {
-         const auto __size = size();
-         const auto __end = _Base::cend();
+         _InvalidateGuard __guard(*this);
          _Base::append_range(__rg);
-         if (size() != __size)
-           this->_M_invalidate_if(_Equal(__end));
        }

     constexpr void
     pop_back()
     {
        __glibcxx_check_nonempty();
-       _M_invalidate_after_nth(_Base::size() - 1);
+       _InvalidateGuard __guard(*this);
        _Base::pop_back();
     }

@@ -341,51 +425,30 @@ namespace __debug
        constexpr optional<_Tp&>
        try_emplace_back(_Args&&... __args)
        {
-         auto __end = _Base::cend();
-         optional<_Tp&> __res
-           = _Base::try_emplace_back(std::forward<_Args>(__args)...);
-
-         if (__res)
-           this->_M_invalidate_if(_Equal(__end));
-
-         return __res;
+         _InvalidateGuard __guard(*this);
+         return _Base::try_emplace_back(std::forward<_Args>(__args)...);
        }

     constexpr optional<_Tp&>
     try_push_back(const _Tp& __x)
     {
-       const auto __end = _Base::cend();
-       optional<_Tp&> __res = _Base::try_push_back(__x);
-
-       if (__res)
-         this->_M_invalidate_if(_Equal(__end));
-
-       return __res;
+       _InvalidateGuard __guard(*this);
+       return _Base::try_push_back(__x);
     }

     constexpr optional<_Tp&>
     try_push_back(_Tp&& __x)
     {
-       const auto __end = _Base::cend();
-       optional<_Tp&> __res = _Base::try_push_back(std::move(__x));
-
-       if (__res)
-         this->_M_invalidate_if(_Equal(__end));
-
-       return __res;
+       _InvalidateGuard __guard(*this);
+       return _Base::try_push_back(std::move(__x));
     }

     template<typename... _Args>
        constexpr _Tp&
        unchecked_emplace_back(_Args&&... __args)
        {

I think the following comment would be useful here:

         // Precondition is checked in _Base::unchecked_emplace_back

Because I was just thinking that the debug inplace_vector should check
the precondition even though this is the "unchecked" API. But it is
already checked with __glibcxx_assert in the base.

-         const auto __end = _Base::cend();
-         _Tp& __res =
-           _Base::unchecked_emplace_back(std::forward<_Args>(__args)...);
-
-         this->_M_invalidate_if(_Equal(__end));
-
-         return __res;
+         _InvalidateGuard __guard(*this);
+         return _Base::unchecked_emplace_back(std::forward<_Args>(__args)...);
        }

     constexpr _Tp&
@@ -407,9 +470,12 @@ namespace __debug

          __glibcxx_check_insert(__position);
          const difference_type __offset = __position.base() - _Base::cbegin();
-         _Base_iterator __res = _Base::emplace(__position.base(),
-                                               std::forward<_Args>(__args)...);
-         _M_invalidate_after_nth(__offset);
+         _Base_iterator __res;
+         {

Why is the extra block scope needed here?

Ah, because otherwise we invalidate the return value as soon as we
construct it? I think that deserves a comment. Arguably, the code was
simpler before. Using the RAII guard take extra lines, and has the
subtle dependency on a new scope. But it does guarantee we invalidate
even if the _Base::emplace call throws.

What if we just called _M_invalidate_after_nth(__offset) before
calling _Base::emplace? Do we lose anything that way?

It's possible that one of the arguments to emplace could be an
iterator into the vector (or something that contains such an iterator)
and invalidating it would break construction of the new element. But I
think that case is undefined anyway, accessing an element of the
vector while emplacing a new element into it violates the reentrancy
rules.

+           _InvalidateAfterNthGuard __guard(*this, __offset);
+           __res = _Base::emplace(__position.base(),
+                                  std::forward<_Args>(__args)...);
+         }
          return { __res, this };

Should the line above be changed to return iterator(__res, this) as
done elsewhere by this patch?

        }

@@ -427,11 +493,14 @@ namespace __debug
        if (std::is_constant_evaluated())

Use 'if consteval' here.

          return iterator(_Base::insert(__position.base(), __n, __x), this);

-         __glibcxx_check_insert(__position);
+       __glibcxx_check_insert(__position);
        const difference_type __offset = __position.base() - _Base::cbegin();
-       _Base_iterator __res = _Base::insert(__position.base(), __n, __x);
-       _M_invalidate_after_nth(__offset);
-       return { __res, this };
+       _Base_iterator __res;
+       {
+         _InvalidateAfterNthGuard __guard(*this, __offset);
+         __res = _Base::insert(__position.base(), __n, __x);
+       }
+       return iterator(__res, this);
     }

     template<__any_input_iterator _InputIterator>
@@ -449,14 +518,16 @@ namespace __debug

          const difference_type __offset = __position.base() - _Base::cbegin();
          _Base_iterator __res;
-         if (__dist.second >= __gnu_debug::__dp_sign)
-           __res = _Base::insert(__position.base(),
-                                 __gnu_debug::__unsafe(__first),
-                                 __gnu_debug::__unsafe(__last));
-         else
-           __res = _Base::insert(__position.base(), __first, __last);
+         {
+           _InvalidateAfterNthGuard __guard(*this, __offset);
+           if (__dist.second >= __gnu_debug::__dp_sign)
+             __res = _Base::insert(__position.base(),
+                                   __gnu_debug::__unsafe(__first),
+                                   __gnu_debug::__unsafe(__last));
+           else
+             __res = _Base::insert(__position.base(), __first, __last);
+         }

-         _M_invalidate_after_nth(__offset);
          return { __res, this };

return iterator(__res, this) ?

        }

@@ -464,11 +535,12 @@ namespace __debug
        constexpr iterator
        insert_range(const_iterator __position, _Rg&& __rg)
        {
-         const auto __size = size();
          const difference_type __offset = __position.base() - _Base::cbegin();
-         auto __res = _Base::insert_range(__position.base(), __rg);
-         if (size() > __size)
-           this->_M_invalidate_after_nth(__offset);
+         _Base_iterator __res;
+         {
+           _InvalidateAfterNthGuard __guard(*this, __offset);
+           __res = _Base::insert_range(__position.base(), __rg);
+         }

          return iterator(__res, this);
        }
@@ -480,11 +552,13 @@ namespace __debug
          return iterator(_Base::insert(__position.base(), __il), this);

        __glibcxx_check_insert(__position);
-       const auto __size = size();
        difference_type __offset = __position.base() - _Base::begin();
-       _Base_iterator __res = _Base::insert(__position.base(), __il);
-       if (size() > __size)
-         this->_M_invalidate_after_nth(__offset);
+       _Base_iterator __res;
+       {
+         _InvalidateAfterNthGuard __guard(*this, __offset);
+         __res = _Base::insert(__position.base(), __il);
+       }
+
        return iterator(__res, this);
     }

@@ -496,8 +570,11 @@ namespace __debug

        __glibcxx_check_erase(__position);
        difference_type __offset = __position.base() - _Base::cbegin();
-       _Base_iterator __res = _Base::erase(__position.base());
-       this->_M_invalidate_after_nth(__offset);
+       _Base_iterator __res;
+       {
+         _InvalidateAfterNthGuard __guard(*this, __offset);
+         __res = _Base::erase(__position.base());
+       }
        return iterator(__res, this);
     }

@@ -509,13 +586,14 @@ namespace __debug
                          this);

        __glibcxx_check_erase_range(__first, __last);
-
        if (__first.base() != __last.base())
          {
            difference_type __offset = __first.base() - _Base::cbegin();
-           _Base_iterator __res = _Base::erase(__first.base(),
-                                               __last.base());
-           this->_M_invalidate_after_nth(__offset);
+           _Base_iterator __res;
+           {
+             _InvalidateAfterNthGuard __guard(*this, __offset);
+             __res = _Base::erase(__first.base(), __last.base());
+           }
            return { __res, this };
          }
        else
diff --git 
a/libstdc++-v3/testsuite/23_containers/inplace_vector/debug/initializer_list.cc 
b/libstdc++-v3/testsuite/23_containers/inplace_vector/debug/initializer_list.cc
new file mode 100644
index 00000000000..35d2916021e
--- /dev/null
+++ 
b/libstdc++-v3/testsuite/23_containers/inplace_vector/debug/initializer_list.cc
@@ -0,0 +1,26 @@
+// { dg-do run { target c++26 } }
+
+#include <debug/inplace_vector>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  __gnu_debug::inplace_vector<int, 6> v = { 0, 1, 2, 3, 4, 5 };
+
+  auto it1 = v.begin() + 1;
+  auto it4 = v.begin() + 4;
+
+  VERIFY( !it1._M_singular() );
+  VERIFY( !it4._M_singular() );
+
+  v = { 10, 11, 12, 13 };
+
+  VERIFY( !it1._M_singular() );
+  VERIFY( it4._M_singular() );
+}
+
+int main()
+{
+  test01();
+}
diff --git 
a/libstdc++-v3/testsuite/23_containers/inplace_vector/debug/initializer_list_pedantic.cc
 
b/libstdc++-v3/testsuite/23_containers/inplace_vector/debug/initializer_list_pedantic.cc
new file mode 100644
index 00000000000..060161e36f2
--- /dev/null
+++ 
b/libstdc++-v3/testsuite/23_containers/inplace_vector/debug/initializer_list_pedantic.cc
@@ -0,0 +1,27 @@
+// { dg-do run { target c++26 } }
+// { dg-options "-D_GLIBCXX_DEBUG_PEDANTIC" }
+
+#include <debug/inplace_vector>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  __gnu_debug::inplace_vector<int, 6> v = { 0, 1, 2, 3, 4, 5 };
+
+  auto it1 = v.begin() + 1;
+  auto it4 = v.begin() + 4;
+
+  VERIFY( !it1._M_singular() );
+  VERIFY( !it4._M_singular() );
+
+  v = { 10, 11, 12, 13 };
+
+  VERIFY( it1._M_singular() );
+  VERIFY( it4._M_singular() );
+}
+
+int main()
+{
+  test01();
+}


Reply via email to