Skip to content
1 change: 1 addition & 0 deletions libs/core/type_support/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ set(type_support_headers
hpx/type_support/extra_data.hpp
hpx/type_support/identity.hpp
hpx/type_support/is_relocatable.hpp
hpx/type_support/is_replaceable.hpp
hpx/type_support/is_trivially_relocatable.hpp
hpx/type_support/is_contiguous_iterator.hpp
hpx/type_support/lazy_conditional.hpp
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) 2025 Isidoros Tsaousis-Seiras
//
// SPDX-License-Identifier: BSL-1.0
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#pragma once

#include <hpx/config.hpp>

#include <cstddef>
#include <type_traits>

#include <hpx/type_support/is_trivially_relocatable.hpp>

Comment thread
guptapratykshh marked this conversation as resolved.
Outdated
namespace hpx::experimental {

// P2786R13 defines a single feature-test macro __cpp_trivial_relocatability
// that covers both the core-language keyword and the associated library traits
// (std::is_trivially_relocatable, std::is_replaceable, std::relocate_at).
// The language and library features are bundled in one proposal, so guarding on
// this macro is sufficient to confirm that std::is_replaceable is available.
#if defined(__cpp_trivial_relocatability)
Comment thread
guptapratykshh marked this conversation as resolved.
Outdated
HPX_CXX_CORE_EXPORT template <typename T>
struct is_replaceable : std::is_replaceable<T>
{
};
Comment thread
guptapratykshh marked this conversation as resolved.
Outdated
#else
HPX_CXX_CORE_EXPORT template <typename T>
struct is_replaceable
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A type is replaceable if what you get from move constructing is the same you get from move assigning.
In code:

T move_assigned = some_t();

T move_constructed(std::move(existing));    // 1
move_assigned = std::move(existing);        // 2

You want move_constructed to be the same as move_assigned, and the conditions you've put forth are not enough to understand if this is true.

Replaceable is an opt-in trait, you might want to take a look at how we deal with such traits in the is_trivially_relocatable definition.

Take a look at this paper to learn more about how to detect replaceability.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are array types not replaceable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arrays are not replaceable because they are not assignable types. while arrays are objects, they do not support move assignment (std::is_trivially_move_assignable_v<T[]> is false), which is core requirement for replaceability according to P2786R13. the explicit specialisations make this exclusion clear and match the expected behavior shown in the test assertions at lines 35-36.

: std::bool_constant<std::is_object_v<T> && !std::is_const_v<T> &&
!std::is_volatile_v<T> &&
(std::is_scalar_v<T> ||
((std::is_class_v<T> || std::is_union_v<T>) &&
std::is_trivially_move_constructible_v<T> &&
std::is_trivially_move_assignable_v<T> &&
std::is_trivially_destructible_v<T>) )>
{
};

HPX_CXX_CORE_EXPORT template <typename T>
struct is_replaceable<T[]> : std::false_type
{
};

HPX_CXX_CORE_EXPORT template <typename T, std::size_t N>
struct is_replaceable<T[N]> : std::false_type
{
};
#endif

HPX_CXX_CORE_EXPORT template <typename T>
inline constexpr bool is_replaceable_v = is_replaceable<T>::value;

} // namespace hpx::experimental
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@

namespace hpx::experimental {

// P2786R13 defines a single feature-test macro __cpp_trivial_relocatability
// that covers both the core-language keyword and the associated library traits
// (std::is_trivially_relocatable, std::is_replaceable, std::relocate_at).
// The language and library features are bundled in one proposal, so guarding on
// this macro is sufficient to confirm that std::is_trivially_relocatable is
// available.
#if defined(__cpp_trivial_relocatability)
Comment thread
guptapratykshh marked this conversation as resolved.
Outdated
template <typename T>
struct is_trivially_relocatable : std::is_trivially_relocatable<T>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should also be added to the replacabillity definition

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feature test macro should also be updated

{
};
#else
Comment thread
guptapratykshh marked this conversation as resolved.
Outdated

// All trivially copyable types are trivially relocatable
// Other types should default to false.
HPX_CXX_CORE_EXPORT template <typename T>
Expand Down Expand Up @@ -74,6 +87,8 @@ namespace hpx::experimental {
{
};

#endif

HPX_CXX_CORE_EXPORT template <typename T>
inline constexpr bool is_trivially_relocatable_v =
is_trivially_relocatable<T>::value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@
#include <cstring>
Comment thread
guptapratykshh marked this conversation as resolved.
#include <type_traits>

#if defined(HPX_HAVE_P1144_STD_RELOCATE_AT)
#include <memory>
#endif

namespace hpx::detail {

// since c++20 std::destroy_at can be used on array types, destructing each
Expand Down Expand Up @@ -118,8 +114,8 @@ namespace hpx::experimental {
// noexcept if the memmove path is taken or if the move path is noexcept
noexcept(detail::relocate_at_helper(src, dst)))
{
static_assert(is_relocatable_v<T>,
"new (dst) T(std::move(*src)) must be well-formed");
static_assert(
is_relocatable_v<T>, "T(std::move(*src)) must be well-formed");

return detail::relocate_at_helper(src, dst);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,6 @@ namespace hpx::experimental::util {
first, last, dst_last, iterators_are_contiguous_default_t{});
}

#endif // defined(__cpp_lib_trivially_relocatable)
#endif // defined(HPX_HAVE_P1144_RELOCATE_AT)

} // namespace hpx::experimental::util
2 changes: 1 addition & 1 deletion libs/core/type_support/tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ endforeach()

if(HPX_WITH_COMPILE_ONLY_TESTS)
# add compile time tests
set(compile_tests is_contiguous_iterator is_relocatable
set(compile_tests is_contiguous_iterator is_relocatable is_replaceable
is_trivially_relocatable
)

Expand Down
128 changes: 128 additions & 0 deletions libs/core/type_support/tests/unit/is_replaceable.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright (c) 2026 Pratyksh Gupta
//
// SPDX-License-Identifier: BSL-1.0
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <hpx/type_support/is_replaceable.hpp>
Comment thread
guptapratykshh marked this conversation as resolved.
Outdated

#include <cassert>
#include <memory>
#include <mutex>
#include <type_traits>

using hpx::experimental::is_replaceable;
using hpx::experimental::is_replaceable_v;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those tests should be updated in accordance with the changes in the implementation of the replacabillity trait.


// Integral types are replaceable (trivially relocatable)
static_assert(is_replaceable_v<int>);
// Const types are not assignable (thus not replaceable)
static_assert(!is_replaceable_v<int const>);

// Pointer types are replaceable if they are not const-qualified themselves.
static_assert(is_replaceable_v<int*>);
static_assert(is_replaceable_v<int const*>);

// Pointers follow the same rules as other objects. A pointer is replaceable only
// if it is not const-qualified itself, as replacement requires assignment.
// P2786R13 Section 3.2: "const-qualified objects are never replaceable."
static_assert(!is_replaceable_v<int* const>);

// Function pointers are replaceable
static_assert(is_replaceable_v<int (*)()>);

// Arrays are not assignable
static_assert(!is_replaceable_v<int[]>);
static_assert(!is_replaceable_v<int[4]>);

// References are not objects
static_assert(!is_replaceable_v<int&>);
static_assert(!is_replaceable_v<int&&>);

// Void types
static_assert(!is_replaceable_v<void>);

// std::mutex is not move assignable, nor trivially relocatable
static_assert(!is_replaceable_v<std::mutex>);

struct trivial_class
{
int x;
};
static_assert(is_replaceable_v<trivial_class>);

// Replaceability implies a type can be destroyed and reconstructed.
// Consequently, a type must be destructible to be replaceable.
// Per P2786R13, implicit replaceability also requires trivial relocatability,
// which in turn requires trivial destructibility.
struct not_destructible
{
not_destructible(not_destructible&&);
~not_destructible() = delete;
};
static_assert(!is_replaceable_v<not_destructible>);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is destructibility a requirement of replaceability?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, implicit replaceability requires trivial relocatability, which requires trivial destructibility


struct not_move_assignable
{
not_move_assignable& operator=(not_move_assignable&&) = delete;
};
static_assert(!is_replaceable_v<not_move_assignable>);

struct not_move_constructible
{
not_move_constructible(not_move_constructible&&) = delete;
not_move_constructible& operator=(not_move_constructible&&);
};
static_assert(!is_replaceable_v<not_move_constructible>);

struct move_assignable_but_not_implicitly_replaceable
{
std::unique_ptr<int> p;
move_assignable_but_not_implicitly_replaceable(
move_assignable_but_not_implicitly_replaceable&&) = default;
move_assignable_but_not_implicitly_replaceable& operator=(
move_assignable_but_not_implicitly_replaceable&&) = default;
};
static_assert(
!is_replaceable_v<move_assignable_but_not_implicitly_replaceable>);

// Opt-in example
struct opt_in_replaceable
{
std::unique_ptr<int> p;
opt_in_replaceable(opt_in_replaceable&&) = default;
opt_in_replaceable& operator=(opt_in_replaceable&&) = default;
};

// Specialize is_replaceable for opt_in_replaceable
namespace hpx::experimental {
template <>
struct is_replaceable<opt_in_replaceable> : std::true_type
{
};
} // namespace hpx::experimental

static_assert(is_replaceable_v<opt_in_replaceable>);

// volatile types are not replaceable because their state is unstable and
// bitwise move-assignment (replacement) cannot guarantee preservation of
// their specific access semantics.
static_assert(!is_replaceable_v<int volatile>);
static_assert(is_replaceable_v<int volatile*>); // pointer to volatile is OK
static_assert(!is_replaceable_v<int* volatile>); // volatile pointer is not

struct has_const_member
{
int const x;
};
// move assignment is deleted for classes with const members
static_assert(!is_replaceable_v<has_const_member>);

struct has_volatile_member
{
int volatile x;
};
// volatile members do not necessarily make move assignment non-trivial
static_assert(is_replaceable_v<has_volatile_member>);

int main() {}
Loading