[PATCH][_GLIBCXX_DEBUG] Enhance inplace_vector implementation

Jonathan Wakely jwakely@redhat.com
Tue Jul 7 13:30:22 GMT 2026


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.

>+      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();
>+}



More information about the Libstdc++ mailing list