[PATCH][_GLIBCXX_DEBUG] Enhance inplace_vector implementation
Jonathan Wakely
jwakely@redhat.com
Wed Jul 8 11:26:15 GMT 2026
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.
>
>>+ 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?
Alternatively, don't change any of the return {...} statements and
leave them using braces (is there a reason to change them?)
>
>> }
>>
>>@@ -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