libstdc++: Fix std::uninitialized_value_construct for arrays [PR120397]

The std::uninitialized_{value,default}_construct{,_n} algorithms should
be able to create arrays, but that currently fails because when an
exception happens they clean up using std::_Destroy and in C++17 that
doesn't support destroying arrays. (For C++20 and later, std::destroy
does handle destroying arrays.)

This commit adjusts the _UninitDestroyGuard RAII type used by those
algos so that in C++17 mode it recursively destroys each rank of an
array type, only using std::_Destroy for the last rank when it's
destroying non-array objects.

libstdc++-v3/ChangeLog:

	PR libstdc++/120397
	* include/bits/stl_uninitialized.h (_UninitDestroyGuard<I,void>):
	Add new member function _S_destroy and call it from the
	destructor (for C++17 only).
	* testsuite/20_util/specialized_algorithms/uninitialized_default_construct/120397.cc:
	New test.
	* testsuite/20_util/specialized_algorithms/uninitialized_value_construct/120397.cc:
	New test.

Reviewed-by: Tomasz Kamiński <tkaminsk@redhat.com>
This commit is contained in:
Jonathan Wakely 2025-05-22 15:42:45 +01:00 committed by Jonathan Wakely
parent 33e3139f5d
commit 3af71d4ac8
No known key found for this signature in database
3 changed files with 58 additions and 0 deletions

View File

@ -118,7 +118,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
~_UninitDestroyGuard()
{
if (__builtin_expect(_M_cur != 0, 0))
#if __cplusplus == 201703L
// std::uninitialized_{value,default}{,_n} can construct array types,
// but std::_Destroy cannot handle them until C++20 (PR 120397).
_S_destroy(_M_first, *_M_cur);
#else
std::_Destroy(_M_first, *_M_cur);
#endif
}
_GLIBCXX20_CONSTEXPR
@ -129,6 +135,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
private:
_UninitDestroyGuard(const _UninitDestroyGuard&);
#if __cplusplus == 201703L
template<typename _Iter>
static void
_S_destroy(_Iter __first, _Iter __last)
{
using _ValT = typename iterator_traits<_Iter>::value_type;
if constexpr (is_array<_ValT>::value)
for (; __first != __last; ++__first)
_S_destroy(*__first, *__first + extent<_ValT>::value);
else
std::_Destroy(__first, __last);
}
#endif
};
// This is the default implementation of std::uninitialized_copy.

View File

@ -0,0 +1,19 @@
// { dg-do compile { target c++17 } }
#include <memory>
// PR libstdc++/120397
// std::uninitialized_value_construct cannot create arrays of non-trivially
// destructible types
struct X { X() { } ~X() { } };
void def(X (*x)[1])
{
std::uninitialized_default_construct(x, x+1);
}
void def_n(X (*x)[1])
{
std::uninitialized_default_construct_n(x, 1);
}

View File

@ -0,0 +1,19 @@
// { dg-do compile { target c++17 } }
#include <memory>
// PR libstdc++/120397
// std::uninitialized_value_construct cannot create arrays of non-trivially
// destructible types
struct X { X() { } ~X() { } };
void val(X (*x)[1])
{
std::uninitialized_value_construct(x, x+1);
}
void val_n(X (*x)[1])
{
std::uninitialized_value_construct_n(x, 1);
}