I have tried using array inside unanimous union instead of local char buffer, but was blocked by following issue: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=121068
The idea here, would be to have a function: template<typename T, size_t N> void star_array_lifetime(T(&arr)[N]) { new(arr) T[N]; for (int i = 0; i < N; ++i) arr[i].~T(); } And the in test instead of operating on char buffer, use array inside anonymous union: union { S a[20]; }; star_array_lifetime(a); After that, the remaining placement new code should work correctly, when PR121068 is fixed. On Mon, Jul 14, 2025 at 3:30 PM Tomasz Kaminski <tkami...@redhat.com> wrote: > > > On Tue, Jun 17, 2025 at 1:15 PM Jakub Jelinek <ja...@redhat.com> wrote: > >> Hi! >> >> Here is a new version of the library side of the C++26 P2786R13 paper. >> For if constexpr the patch uses __builtin_constant_p trick to figure >> out if __result is non-equality comparable with __first, it adds recursion >> for the is_array_v cases, adds qualification on several calls and rewrites >> the testcase, such that it is hopefully valid and also tests the constant >> evaluation. >> >> 2025-06-17 Jakub Jelinek <ja...@redhat.com> >> >> PR c++/119064 >> * include/bits/version.def (trivially_relocatable): New. >> * include/bits/version.h: Regenerate. >> * include/std/type_traits (std::is_trivially_relocatable, >> std::is_nothrow_relocatable, std::is_replaceable): New traits. >> std::is_trivially_relocatable_v, std::is_nothrow_relocatable_v, >> std::is_replaceable_v): New trait variable templates. >> * include/std/memory (__glibcxx_want_trivially_relocatable): >> Define >> before including bits/version.h. >> (std::trivially_relocate): New template function. >> (std::relocate): Likewise. >> * testsuite/std/memory/relocate/relocate.cc: New test. >> >> --- libstdc++-v3/include/bits/version.def.jj 2025-06-12 >> 20:19:52.367395730 +0200 >> +++ libstdc++-v3/include/bits/version.def 2025-06-16 >> 22:10:09.415721974 +0200 >> @@ -2012,6 +2012,15 @@ ftms = { >> }; >> }; >> >> +ftms = { >> + name = trivially_relocatable; >> + values = { >> + v = 202502; >> + cxxmin = 26; >> + extra_cond = "__cpp_trivial_relocatability >= 202502L"; >> + }; >> +}; >> + >> // Standard test specifications. >> stds[97] = ">= 199711L"; >> stds[03] = ">= 199711L"; >> --- libstdc++-v3/include/bits/version.h.jj 2025-06-12 >> 20:19:52.367395730 +0200 >> +++ libstdc++-v3/include/bits/version.h 2025-06-16 22:10:09.416721960 >> +0200 >> @@ -2253,4 +2253,14 @@ >> #endif /* !defined(__cpp_lib_sstream_from_string_view) && >> defined(__glibcxx_want_sstream_from_string_view) */ >> #undef __glibcxx_want_sstream_from_string_view >> >> +#if !defined(__cpp_lib_trivially_relocatable) >> +# if (__cplusplus > 202302L) && (__cpp_trivial_relocatability >= >> 202502L) >> +# define __glibcxx_trivially_relocatable 202502L >> +# if defined(__glibcxx_want_all) || >> defined(__glibcxx_want_trivially_relocatable) >> +# define __cpp_lib_trivially_relocatable 202502L >> +# endif >> +# endif >> +#endif /* !defined(__cpp_lib_trivially_relocatable) && >> defined(__glibcxx_want_trivially_relocatable) */ >> +#undef __glibcxx_want_trivially_relocatable >> + >> #undef __glibcxx_want_all >> --- libstdc++-v3/include/std/type_traits.jj 2025-06-12 >> 00:20:03.898666479 +0200 >> +++ libstdc++-v3/include/std/type_traits 2025-06-16 >> 22:10:09.416721960 +0200 >> @@ -4245,6 +4245,60 @@ template<typename _Ret, typename _Fn, ty >> >> #endif // C++2a >> >> +#if __glibcxx_trivially_relocatable >= 202502L // C++ >= 26 && >> __cpp_trivial_relocatability >= 202502 >> + /// True if the type is a trivially relocatable type. >> + /// @since C++26 >> + >> + template<typename _Tp> >> + struct is_trivially_relocatable >> +# if __has_builtin(__builtin_is_trivially_relocatable) >> + : bool_constant<__builtin_is_trivially_relocatable(_Tp)> >> +# else >> + : bool_constant<__builtin_is_cpp_trivially_relocatable(_Tp)> >> +# endif >> + { }; >> + >> + template<typename _Tp> >> + struct is_nothrow_relocatable >> +# if _GLIBCXX_USE_BUILTIN_TRAIT(__builtin_is_nothrow_relocatable) >> + : bool_constant<__builtin_is_nothrow_relocatable(_Tp)> >> +# else >> + : public __or_<is_trivially_relocatable<_Tp>, >> + >> __and_<is_nothrow_move_constructible<remove_all_extents<_Tp>>, >> + >> is_nothrow_destructible<remove_all_extends<_Tp>>>>::type >> +# endif >> + { }; >> + >> + template<typename _Tp> >> + struct is_replaceable >> + : bool_constant<__builtin_is_replaceable(_Tp)> >> + { }; >> + >> + /// @ingroup variable_templates >> + /// @since C++26 >> + template<typename _Tp> >> + inline constexpr bool is_trivially_relocatable_v >> +# if __has_builtin(__builtin_is_trivially_relocatable) >> + = __builtin_is_trivially_relocatable(_Tp); >> +# else >> + = __builtin_is_cpp_trivially_relocatable(_Tp); >> +# endif >> + >> + template<typename _Tp> >> + inline constexpr bool is_nothrow_relocatable_v >> +# if _GLIBCXX_USE_BUILTIN_TRAIT(__builtin_is_nothrow_relocatable) >> + = __builtin_is_nothrow_relocatable(_Tp); >> +# else >> + = (is_trivially_relocatable_v<_Tp> >> + || (is_nothrow_move_constructible_v<remove_all_extents<_Tp>> >> + && is_nothrow_destructible_v<remove_all_extents<_Tp>>); >> +# endif >> + >> + template<typename _Tp> >> + inline constexpr bool is_replaceable_v >> + = __builtin_is_replaceable(_Tp); >> +#endif >> + >> /// @} group metaprogramming >> >> _GLIBCXX_END_NAMESPACE_VERSION >> --- libstdc++-v3/include/std/memory.jj 2025-06-02 21:58:22.148775743 >> +0200 >> +++ libstdc++-v3/include/std/memory 2025-06-17 12:03:01.249069663 >> +0200 >> @@ -121,6 +121,7 @@ >> #define __glibcxx_want_smart_ptr_for_overwrite >> #define __glibcxx_want_to_address >> #define __glibcxx_want_transparent_operators >> +#define __glibcxx_want_trivially_relocatable >> #include <bits/version.h> >> >> #if __cplusplus >= 201103L && __cplusplus <= 202002L && _GLIBCXX_HOSTED >> @@ -170,6 +171,121 @@ _GLIBCXX_END_NAMESPACE_VERSION >> } // namespace >> #endif // C++11 to C++20 >> >> +#ifdef __cpp_lib_trivially_relocatable >> +namespace std _GLIBCXX_VISIBILITY(default) >> +{ >> +_GLIBCXX_BEGIN_NAMESPACE_VERSION >> + >> + template<typename _Tp> >> + [[__gnu__::__always_inline__]] >> + inline _Tp* >> + trivially_relocate(_Tp* __first, _Tp* __last, _Tp* __result) noexcept >> + { >> + static_assert(is_trivially_relocatable_v<_Tp> && !is_const_v<_Tp>); >> + if (__first == __result) >> + return __last; >> +#if __has_builtin(__builtin_trivially_relocate) >> + __builtin_trivially_relocate(__result, __first, __last - __first); >> +#else >> + __builtin_memmove(static_cast<void*>(__result), >> + static_cast<const void*>(__first), >> + (__last - __first) * sizeof(_Tp)); >> +#endif >> + return __result + (__last - __first); >> + } >> + >> + template<typename _Tp> >> + constexpr _Tp* >> + relocate(_Tp* __first, _Tp* __last, _Tp* __result) noexcept >> + { >> + static_assert(is_nothrow_relocatable_v<_Tp> && !is_const_v<_Tp>); >> + if !consteval >> + { >> + if constexpr (is_trivially_relocatable_v<_Tp>) >> + return std::trivially_relocate(__first, __last, __result); >> + } >> + if (__first == __result) >> + return __last; >> + if (__first == __last) >> + return __result; >> + >> + size_t __n = __last - __first; >> + if constexpr (is_move_constructible_v<_Tp>) >> + { >> + if !consteval >> + { >> + // If there is no overlap, move everything first and then >> + // destruct. >> + if ((__UINTPTR_TYPE__)__last <= (__UINTPTR_TYPE__)__result >> + || ((__UINTPTR_TYPE__)(__result + __n) >> + <= (__UINTPTR_TYPE__)__first)) >> + { >> + __result = std::uninitialized_move(__first, __last, >> + __result); >> + std::destroy(__first, __last); >> + return __result; >> + } >> + } >> + } >> + >> + bool __fwd = true; >> + if consteval >> + { >> + // Use __builtin_constant_p to determine if __result and __first >> + // point into the same object, if they don't, there is no >> overlap >> + // and so either __fwd or !__fwd is fine. >> + if (__builtin_constant_p (__result < __first) >> + && __result > __first >> + && __result < __last) >> + __fwd = false; >> + } >> + else >> + { >> + __fwd = ((__UINTPTR_TYPE__)__result - (__UINTPTR_TYPE__)__first >> + >= (__UINTPTR_TYPE__)__last - >> (__UINTPTR_TYPE__)__first); >> + } >> + if (__builtin_expect(__fwd, true)) >> + { >> + for (; __first != __last; ++__first, ++__result) >> + { >> + if constexpr (is_array_v<_Tp>) >> + std::relocate(std::begin(*__first), std::end(*__first), >> + &(*__result)[0]); >> + else >> + { >> + ::new(__result) _Tp(std::move(*__first)); >> + __first->~_Tp(); >> + } >> + } >> + return __result; >> + } >> + else >> + { >> + _Tp *__ret = __result + __n; >> + for (__result = __ret; __last != __first; ) >> + { >> + --__last; >> + --__result; >> + if constexpr (is_array_v<_Tp>) >> + std::relocate(std::begin(*__last), std::end(*__last), >> + &(*__result)[0]); >> + else >> + { >> + ::new(__result) _Tp(std::move(*__last)); >> + __last->~_Tp(); >> + } >> + } >> + return __ret; >> + } >> + // If at constand evaluation is_trivially_relocatable_v<_Tp> >> + // but not is_move_constructible_V<_Tp>, fail. >> + __builtin_unreachable(); >> + } >> + >> +_GLIBCXX_END_NAMESPACE_VERSION >> +} // namespace >> +#endif >> + >> #ifdef __cpp_lib_parallel_algorithm // C++ >= 17 && HOSTED >> // Parallel STL algorithms >> # if _PSTL_EXECUTION_POLICIES_DEFINED >> --- libstdc++-v3/testsuite/std/memory/relocate/relocate.cc.jj >> 2025-06-16 22:10:09.417721947 +0200 >> +++ libstdc++-v3/testsuite/std/memory/relocate/relocate.cc >> 2025-06-17 13:03:48.309023611 +0200 >> @@ -0,0 +1,305 @@ >> +// { dg-do run { target c++26 } } >> + >> +#include <memory> >> + >> +#include <testsuite_hooks.h> >> +#include <testsuite_allocator.h> >> + >> +struct S trivially_relocatable_if_eligible >> +{ >> + S() : s(0) {} >> + S(int x) : s(x) {} >> + S(S&& x) : s(x.s) {} >> + S& operator=(S&& x) { s = x.s; return *this; } >> + unsigned char s; >> +}; >> + >> +struct T >> +{ >> + T() : t(0) {} >> + T(int x) : t(x) {} >> + T(T&& x) noexcept : t(x.t) {} >> + T& operator=(T&& x) { t = x.t; return *this; } >> + unsigned char t; >> +}; >> + >> +static_assert(std::is_trivially_relocatable_v<S>); >> +static_assert(std::is_nothrow_relocatable_v<S>); >> +static_assert(std::is_nothrow_relocatable_v<T>); >> + >> +constexpr void >> +test_relocate() >> +{ >> + unsigned char a[20], b[20], c[20], d[20]; >> + S *sf, *sl, *sr; >> + T *tf, *tl, *tr; >> + sf = ::new(&a[2]) S(11); >> + tf = ::new(&b[2]) T(11); >> + for (int i = 3; i < 8; ++i) >> + { >> + sl = ::new(&a[i]) S(i + 9); >> + tl = ::new(&b[i]) T(i + 9); >> + } >> + ++sl; >> + ++tl; >> + sr = ::new(&c[5]) S(42); >> + tr = ::new(&d[5]) T(42); >> + sr->~S(); >> + tr->~T(); >> + VERIFY( std::relocate(sf, sl, sr) == sr + 6 ); >> + VERIFY( std::relocate(tf, tl, tr) == tr + 6 ); >> + for (int i = 0; i < 6; ++i) >> + { >> + VERIFY( sr->s == i + 11 ); >> + VERIFY( tr->t == i + 11 ); >> + sr->~S(); >> + tr->~T(); >> + ++sr; >> + ++tr; >> + } >> + sf = ::new(&a[2]) S(11); >> + tf = ::new(&b[2]) T(11); >> + for (int i = 3; i < 8; ++i) >> + { >> + sl = ::new(&a[i]) S(i + 9); >> + tl = ::new(&b[i]) T(i + 9); >> + } >> + ++sl; >> + ++tl; >> + sr = ::new(&a[1]) S(42); >> + tr = ::new(&a[1]) T(42); >> + sr->~S(); >> + tr->~T(); >> + VERIFY( std::relocate(sf, sl, sr) == sr + 6 ); >> + VERIFY( std::relocate(tf, tl, tr) == tr + 6 ); >> + for (int i = 0; i < 6; ++i) >> + { >> + VERIFY( sr->s == i + 11 ); >> + VERIFY( tr->t == i + 11 ); >> + sr->~S(); >> + tr->~T(); >> + ++sr; >> + ++tr; >> + } >> + sf = ::new(&a[2]) S(11); >> + tf = ::new(&b[2]) T(11); >> + for (int i = 3; i < 8; ++i) >> + { >> + sl = ::new(&a[i]) S(i + 9); >> + tl = ::new(&b[i]) T(i + 9); >> + } >> + ++sl; >> + ++tl; >> + sr = sf + 1; >> + tr = tf + 1; >> + VERIFY( std::relocate(sf, sl, sr) == sr + 6 ); >> + VERIFY( std::relocate(tf, tl, tr) == tr + 6 ); >> + for (int i = 0; i < 6; ++i) >> + { >> + VERIFY( sr->s == i + 11 ); >> + VERIFY( tr->t == i + 11 ); >> + sr->~S(); >> + tr->~T(); >> + ++sr; >> + ++tr; >> + } >> +} >> + >> +constexpr void >> +test_relocate_array() >> +{ >> + unsigned char a[20], b[20], c[20], d[20]; >> + using SA = S[2]; >> + using TA = T[2]; >> + SA *sf, *sl, *sr; >> + TA *tf, *tl, *tr; >> + sf = (SA *)(::new(&a[2]) SA{11, 12}); >> + tf = (TA *)(::new(&b[2]) TA{11, 12}); >> + for (int i = 0; i < 5; ++i) >> + { >> + sl = (SA *)(::new(&a[4 + 2 * i]) SA{13 + 2 * i, 14 + 2 * i}); >> + tl = (TA *)(::new(&b[4 + 2 * i]) TA{13 + 2 * i, 14 + 2 * i}); >> + } >> + ++sl; >> + ++tl; >> + sr = (SA *)(::new(&c[6]) SA{42, 42}); >> + tr = (TA *)(::new(&d[6]) TA{42, 42}); >> + (*sr)[0].~S(); >> + (*sr)[1].~S(); >> + (*tr)[0].~T(); >> + (*tr)[1].~T(); >> + VERIFY( std::relocate(sf, sl, sr) == sr + 6 ); >> + VERIFY( std::relocate(tf, tl, tr) == tr + 6 ); >> + for (int i = 0; i < 6; ++i) >> + { >> + VERIFY( (*sr)[0].s == 2 * i + 11 && (*sr)[1].s == 2 * i + 12 ); >> + VERIFY( (*tr)[0].t == 2 * i + 11 && (*tr)[1].t == 2 * i + 12 ); >> + (*sr)[0].~S(); >> + (*sr)[1].~S(); >> + (*tr)[0].~T(); >> + (*tr)[1].~T(); >> + ++sr; >> + ++tr; >> + } >> + sf = (SA *)(::new(&a[2]) SA{11, 12}); >> + tf = (TA *)(::new(&b[2]) TA{11, 12}); >> + for (int i = 0; i < 5; ++i) >> + { >> + sl = (SA *)(::new(&a[4 + 2 * i]) SA{13 + 2 * i, 14 + 2 * i}); >> + tl = (TA *)(::new(&b[4 + 2 * i]) TA{13 + 2 * i, 14 + 2 * i}); >> + } >> + ++sl; >> + ++tl; >> + sr = (SA *)(::new(&a[0]) SA{42, 42}); >> + tr = (TA *)(::new(&b[0]) TA{42, 42}); >> + (*sr)[0].~S(); >> + (*sr)[1].~S(); >> + (*tr)[0].~T(); >> + (*tr)[1].~T(); >> + VERIFY( std::relocate(sf, sl, sr) == sr + 6 ); >> + VERIFY( std::relocate(tf, tl, tr) == tr + 6 ); >> + for (int i = 0; i < 6; ++i) >> + { >> + VERIFY( (*sr)[0].s == 2 * i + 11 && (*sr)[1].s == 2 * i + 12 ); >> + VERIFY( (*tr)[0].t == 2 * i + 11 && (*tr)[1].t == 2 * i + 12 ); >> + (*sr)[0].~S(); >> + (*sr)[1].~S(); >> + (*tr)[0].~T(); >> + (*tr)[1].~T(); >> + ++sr; >> + ++tr; >> + } >> + sf = (SA *)(::new(&a[2]) SA{11, 12}); >> + tf = (TA *)(::new(&b[2]) TA{11, 12}); >> + for (int i = 0; i < 5; ++i) >> + { >> + sl = (SA *)(::new(&a[4 + 2 * i]) SA{13 + 2 * i, 14 + 2 * i}); >> + tl = (TA *)(::new(&b[4 + 2 * i]) TA{13 + 2 * i, 14 + 2 * i}); >> + } >> + ++sl; >> + ++tl; >> + sr = sf + 1; >> + tr = tf + 1; >> + VERIFY( std::relocate(sf, sl, sr) == sr + 6 ); >> + VERIFY( std::relocate(tf, tl, tr) == tr + 6 ); >> + for (int i = 0; i < 6; ++i) >> + { >> + VERIFY( (*sr)[0].s == 2 * i + 11 && (*sr)[1].s == 2 * i + 12 ); >> + VERIFY( (*tr)[0].t == 2 * i + 11 && (*tr)[1].t == 2 * i + 12 ); >> + (*sr)[0].~S(); >> + (*sr)[1].~S(); >> + (*tr)[0].~T(); >> + (*tr)[1].~T(); >> + ++sr; >> + ++tr; >> + } >> +} >> + >> +void >> +test_trivially_relocate() >> +{ >> + unsigned char a[20], c[20]; >> + S *sf, *sl, *sr; >> + sf = ::new(&a[2]) S(11); >> + for (int i = 3; i < 8; ++i) >> + sl = ::new(&a[i]) S(i + 9); >> + ++sl; >> + sr = ::new(&c[5]) S(42); >> + sr->~S(); >> + VERIFY( std::trivially_relocate(sf, sl, sr) == sr + 6 ); >> + for (int i = 0; i < 6; ++i) >> + { >> + VERIFY( sr->s == i + 11 ); >> + sr->~S(); >> + ++sr; >> + } >> + sf = ::new(&a[2]) S(11); >> + for (int i = 3; i < 8; ++i) >> + sl = ::new(&a[i]) S(i + 9); >> + ++sl; >> + sr = ::new(&a[1]) S(42); >> + sr->~S(); >> + VERIFY( std::trivially_relocate(sf, sl, sr) == sr + 6 ); >> + for (int i = 0; i < 6; ++i) >> + { >> + VERIFY( sr->s == i + 11 ); >> + sr->~S(); >> + ++sr; >> + } >> + sf = ::new(&a[2]) S(11); >> + for (int i = 3; i < 8; ++i) >> + sl = ::new(&a[i]) S(i + 9); >> + ++sl; >> + sr = sf + 1; >> + VERIFY( std::trivially_relocate(sf, sl, sr) == sr + 6 ); >> + for (int i = 0; i < 6; ++i) >> + { >> + VERIFY( sr->s == i + 11 ); >> + sr->~S(); >> + ++sr; >> + } >> +} >> + >> +void >> +test_trivially_relocate_array() >> +{ >> + unsigned char a[20], c[20]; >> + using SA = S[2]; >> + SA *sf, *sl, *sr; >> + sf = (SA *)(::new(&a[2]) SA{11, 12}); >> + for (int i = 0; i < 5; ++i) >> + sl = (SA *)(::new(&a[4 + 2 * i]) SA{13 + 2 * i, 14 + 2 * i}); >> + ++sl; >> + sr = (SA *)(::new(&c[6]) SA{42, 42}); >> + (*sr)[0].~S(); >> + (*sr)[1].~S(); >> + VERIFY( std::trivially_relocate(sf, sl, sr) == sr + 6 ); >> + for (int i = 0; i < 6; ++i) >> + { >> + VERIFY( (*sr)[0].s == 2 * i + 11 && (*sr)[1].s == 2 * i + 12 ); >> + (*sr)[0].~S(); >> + (*sr)[1].~S(); >> + ++sr; >> + } >> + sf = (SA *)(::new(&a[2]) SA{11, 12}); >> + for (int i = 0; i < 5; ++i) >> + sl = (SA *)(::new(&a[4 + 2 * i]) SA{13 + 2 * i, 14 + 2 * i}); >> + ++sl; >> + sr = (SA *)(::new(&a[0]) SA{42, 42}); >> + (*sr)[0].~S(); >> + (*sr)[1].~S(); >> + VERIFY( std::trivially_relocate(sf, sl, sr) == sr + 6 ); >> + for (int i = 0; i < 6; ++i) >> + { >> + VERIFY( (*sr)[0].s == 2 * i + 11 && (*sr)[1].s == 2 * i + 12 ); >> + (*sr)[0].~S(); >> + (*sr)[1].~S(); >> + ++sr; >> + } >> + sf = (SA *)(::new(&a[2]) SA{11, 12}); >> + for (int i = 0; i < 5; ++i) >> + sl = (SA *)(::new(&a[4 + 2 * i]) SA{13 + 2 * i, 14 + 2 * i}); >> + ++sl; >> + sr = sf + 1; >> + VERIFY( std::trivially_relocate(sf, sl, sr) == sr + 6 ); >> + for (int i = 0; i < 6; ++i) >> + { >> + VERIFY( (*sr)[0].s == 2 * i + 11 && (*sr)[1].s == 2 * i + 12 ); >> + (*sr)[0].~S(); >> + (*sr)[1].~S(); >> + ++sr; >> + } >> +} >> + >> +int main() >> +{ >> + test_relocate(); >> + test_trivially_relocate(); >> + test_relocate_array(); >> + test_trivially_relocate_array(); >> + static_assert([] { >> + test_relocate(); >> + test_relocate_array(); >> + return true; >> + }); > > The lambda is not invoked here, i.e. we should have "}())", and we are now > testing > if the conversion to funciton pointer produces a non-null pointer, which > is true. After fixing > the line, I get a lot of errors about missing constexpr on S and T > constructors. > After that, the placement of new calls are failing and reinterpret_cast. > > I am not yet sure how to write a constexpr compatible test for this > functionality. > >> > > +} >> >> Jakub >> >>