[RFC PATCH] libstdc++: WIP on library side of C++26 P2786R13 - Trivial Relocatability
Jakub Jelinek
jakub@redhat.com
Mon Jun 16 18:21:15 GMT 2025
Hi!
The following patch is an attempt to implement the library side of
C++26 P2786R13 - Trivial Relocatability paper on top of the
https://gcc.gnu.org/pipermail/gcc-patches/2025-June/686856.html
compiler side.
The testcase is really bad (has UB in it), because the code
moves and destructs stuff but then attempts to test also the
destructed elements, so I'm afraid it needs to be rewritten pretty
much from scratch. Maybe it isn't even ok to destruct elements
from automatic array and then construct stuff back, so maybe it
needs to be done in some heap allocation instead. Even the move
construction into already constructed elts is UB.
Anyway, I've run into a major obstacle.
std::trivially_relocate is fine, it isn't constexpr, so can use
less on pointers just fine (I didn't want to include further headers,
so added the casts directly), but
std::relocate is supposed to be constexpr, and we need to perform
[__first, __last) vs. [__result, __result + (__last - __first)) range
overlap test even at compile time. Right now the patch uses
even for such test
if ((__UINTPTR_TYPE__)__result < (__UINTPTR_TYPE__)__first
|| (__UINTPTR_TYPE__)__last < (__UINTPTR_TYPE__)__result)
which of course doesn't work at constant evaluation time, but even
bool __forw = false;
if consteval
{
forw = __result < __first || __last < __result;
}
else
{
forw = ((__UINTPTR_TYPE__)__result < (__UINTPTR_TYPE__)__first
|| (__UINTPTR_TYPE__)__last < (__UINTPTR_TYPE__)__result);
}
if (forw)
would only work if all of __first, __last and __result point into the same
object. __first and __last obviously have to point to the same object,
but __result could be a different one.
Any thoughts what to do?
New builtin that would perform such an overlap check, or implement less
that would work reliably at constant evaluation time, or a builtin that
would allow to test if two pointers point into the same array at constant
evaluation time (if so, then it could use normal < comparisons, if not so,
then there is surely no overlap and so the direction doesn't matter).
Thoughts on this?
2025-06-16 Jakub Jelinek <jakub@redhat.com>
* 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 19:51:32.738009472 +0200
+++ libstdc++-v3/include/bits/version.def 2025-06-16 16:26:56.031533123 +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 19:51:32.747009354 +0200
+++ libstdc++-v3/include/bits/version.h 2025-06-16 16:27:05.244271592 +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 09:49:19.924910752 +0200
+++ libstdc++-v3/include/std/type_traits 2025-06-16 18:57:28.746149958 +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-03 07:49:38.278638754 +0200
+++ libstdc++-v3/include/std/memory 2025-06-16 19:45:33.031987440 +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,96 @@ _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 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 = uninitialized_move(__first, __last, __result);
+ destroy(__first, __last);
+ return __result;
+ }
+ }
+ }
+
+ // FIXME: This won't work at constexpr time. std::less uses
+ // there just < without the cast which won't work either if
+ // __result and __first don't point into the same object.
+ if ((__UINTPTR_TYPE__)__result < (__UINTPTR_TYPE__)__first
+ || (__UINTPTR_TYPE__)__last < (__UINTPTR_TYPE__)__result)
+ {
+ for (; __first != __last; ++__first, ++__result)
+ {
+ ::new(__result) _Tp(move(*__first));
+ __first->~_Tp();
+ }
+ return __result;
+ }
+ else
+ {
+ _Tp *__ret = __result + __n;
+ for (__result = __ret; __last != __first; )
+ {
+ --__last;
+ --__result;
+ ::new(__result) _Tp(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 18:49:44.558215115 +0200
+++ libstdc++-v3/testsuite/std/memory/relocate/relocate.cc 2025-06-16 19:49:12.527134718 +0200
@@ -0,0 +1,71 @@
+// { 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; }
+ int 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; }
+ int 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()
+{
+ S s[20] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ T t[20] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ VERIFY( std::relocate(&s[2], &s[8], &s[10]) == &s[16] );
+ VERIFY( std::relocate(&t[2], &t[8], &t[10]) == &t[16] );
+ for (int i = 0; i < 20; ++i)
+ {
+ VERIFY( s[i].s == (i < 10 ? i + 1 : i < 16 ? i - 7 : 0) );
+ VERIFY( t[i].t == s[i].s );
+ }
+ VERIFY( std::relocate(&s[8], &s[12], &s[9]) == &s[13] );
+ VERIFY( std::relocate(&t[8], &t[12], &t[9]) == &t[13] );
+ for (int i = 0; i < 20; ++i)
+ {
+ VERIFY( s[i].s == (i < 9 ? i + 1 : i < 11 ? i : i < 13 ? i - 8
+ : i < 16 ? i - 7 : 0) );
+ VERIFY( t[i].t == s[i].s );
+ }
+ VERIFY( std::relocate(&s[8], &s[12], &s[7]) == &s[11] );
+ VERIFY( std::relocate(&t[8], &t[12], &t[7]) == &t[11] );
+}
+
+constexpr void
+test_trivially_relocate()
+{
+ S s[20] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ VERIFY( std::trivially_relocate(&s[2], &s[8], &s[10]) == &s[16] );
+ for (int i = 0; i < 20; ++i)
+ VERIFY( s[i].s == (i < 10 ? i + 1 : i < 16 ? i - 7 : 0) );
+ VERIFY( std::relocate(&s[8], &s[12], &s[9]) == &s[13] );
+ for (int i = 0; i < 20; ++i)
+ VERIFY( s[i].s == (i < 9 ? i + 1 : i < 11 ? i : i < 13 ? i - 8
+ : i < 16 ? i - 7 : 0) );
+}
+
+int main()
+{
+ test_relocate();
+ test_trivially_relocate();
+}
Jakub
More information about the Libstdc++
mailing list