Date: Tue, 28 Oct 2025 12:23:16 +0000
On Tue, Oct 28, 2025 at 12:16 PM Frederick Virchanza Gotham wrote:
>
> Here's code I shared earlier in the week on this mailing list:
>
> https://godbolt.org/z/nbeKMKe1o
Sorry, disregard my last post, as it only constructs another T in the
place of an original T.
The following code allows the construction of a different type in
place of the original T:
https://godbolt.org/z/oPKb977qb
(Of course it assumes that the memory alignment is correct)
And here it is copy-pasted:
#include <cassert> // assert
#include <cstddef> // byte
#include <cstring> // memcpy
#include <algorithm> // swap_ranges
#include <memory> // construct_at, destroy_at
#include <type_traits> // type traits
#include <utility> // forward, move
#ifdef NDEBUG
# define CRASH_IN_DEBUG_MODE() /* nothing */
#else
# define CRASH_IN_DEBUG_MODE() assert(nullptr == "Destructor
threw an exception!")
#endif
#if defined(HAS_STD_RELOCATE_AT) && defined(HAS_STD_IS_NOTHROW_RELOCATABLE)
# define CAN_USE_STD_RELOCATE 1
#else
# define CAN_USE_STD_RELOCATE 0
#endif
template<typename T, typename U = T, typename... Args>
requires (!std::is_const_v<T> && !std::is_volatile_v<T>)
void replace(T *const where, Args&&... args)
noexcept(std::is_nothrow_constructible_v<U, Args...>)
{
using std::construct_at;
using std::destroy_at;
if constexpr ( std::is_nothrow_constructible_v<U, Args...> )
{
try { destroy_at(where); } catch (...) { CRASH_IN_DEBUG_MODE(); }
construct_at(reinterpret_cast<U*>(where), std::forward<Args>(args)...);
}
#if CAN_USE_STD_RELOCATE
else if constexpr ( true )
{
alignas(T) std::byte elsewhere[sizeof(T)];
T *const elsewhereT = static_cast<T*>(static_cast<void*>(elsewhere));
// Relocation is supported (non-standard extension)
if constexpr ( std::is_nothrow_relocatable_v<T> )
{
std::relocate_at(where, elsewhereT);
try { construct_at(reinterpret_cast<U*>(where),
std::forward<Args>(args)...); }
catch (...)
{
std::relocate_at(elsewhereT, where);
throw;
}
try { destroy_at(elsewhereT); } catch (...) {
CRASH_IN_DEBUG_MODE(); }
}
else if constexpr ( std::is_nothrow_move_constructible_v<T> )
{
// Relocation exists but isn't noexcept — fallback to move
construct_at(elsewhereT, std::move(*where));
try { destroy_at(where); } catch (...) { CRASH_IN_DEBUG_MODE(); }
try { construct_at(reinterpret_cast<U*>(where),
std::forward<Args>(args)...); }
catch (...)
{
construct_at(where, std::move(*elsewhereT));
try { destroy_at(elsewhereT); } catch (...) {
CRASH_IN_DEBUG_MODE(); }
throw;
}
try { destroy_at(elsewhereT); } catch (...) {
CRASH_IN_DEBUG_MODE(); }
}
else
{
static_assert( false == requires { typename
T::tag_never_retain; } );
using std::memcpy;
std::byte elsewhere[sizeof(T)];
memcpy(elsewhere, where, sizeof *elsewhere);
try { construct_at(reinterpret_cast<U*>(where),
std::forward<Args>(args)...); }
catch (...)
{
memcpy(where, elsewhere, sizeof *where);
throw;
}
std::swap_ranges(
elsewhere,
elsewhere + sizeof elsewhere,
static_cast<std::byte*>(static_cast<void*>(where))
);
try { destroy_at(where); } catch (...) { CRASH_IN_DEBUG_MODE(); }
memcpy(where, elsewhere, sizeof *where);
}
}
#endif // if CAN_USE_STD_RELOCATE
else if constexpr (std::is_nothrow_move_constructible_v<T>)
{
// No relocation support; fallback to move construction
alignas(T) std::byte elsewhere[sizeof(T)];
T *const elsewhereT = static_cast<T*>(static_cast<void*>(elsewhere));
construct_at(elsewhereT, std::move(*where));
try { destroy_at(where); } catch (...) { CRASH_IN_DEBUG_MODE(); }
try { construct_at(reinterpret_cast<U*>(where),
std::forward<Args>(args)...); }
catch (...)
{
construct_at(where, std::move(*elsewhereT));
try { destroy_at(elsewhereT); } catch (...) {
CRASH_IN_DEBUG_MODE(); }
throw;
}
try { destroy_at(elsewhereT); } catch (...) { CRASH_IN_DEBUG_MODE(); }
}
else
{
static_assert( false == requires { typename T::tag_never_retain; } );
using std::memcpy;
std::byte elsewhere[sizeof(T)];
memcpy(elsewhere, where, sizeof *elsewhere);
try { construct_at(reinterpret_cast<U*>(where),
std::forward<Args>(args)...); }
catch (...)
{
memcpy(where, elsewhere, sizeof *where);
throw;
}
std::swap_ranges(
elsewhere,
elsewhere + sizeof elsewhere,
static_cast<std::byte*>(static_cast<void*>(where))
);
try { destroy_at(where); } catch (...) { CRASH_IN_DEBUG_MODE(); }
memcpy(where, elsewhere, sizeof *where);
}
}
#include <string>
int main(void)
{
struct Monkey {
Monkey(void) noexcept(false) = default;
Monkey(Monkey&) = delete;
Monkey(Monkey&&) = delete;
typedef int tag_never_retain2; // try removing the '2' here
};
Monkey var;
replace(&var);
std::string str;
replace(&str);
replace<std::string,Monkey>(&str);
}
>
> Here's code I shared earlier in the week on this mailing list:
>
> https://godbolt.org/z/nbeKMKe1o
Sorry, disregard my last post, as it only constructs another T in the
place of an original T.
The following code allows the construction of a different type in
place of the original T:
https://godbolt.org/z/oPKb977qb
(Of course it assumes that the memory alignment is correct)
And here it is copy-pasted:
#include <cassert> // assert
#include <cstddef> // byte
#include <cstring> // memcpy
#include <algorithm> // swap_ranges
#include <memory> // construct_at, destroy_at
#include <type_traits> // type traits
#include <utility> // forward, move
#ifdef NDEBUG
# define CRASH_IN_DEBUG_MODE() /* nothing */
#else
# define CRASH_IN_DEBUG_MODE() assert(nullptr == "Destructor
threw an exception!")
#endif
#if defined(HAS_STD_RELOCATE_AT) && defined(HAS_STD_IS_NOTHROW_RELOCATABLE)
# define CAN_USE_STD_RELOCATE 1
#else
# define CAN_USE_STD_RELOCATE 0
#endif
template<typename T, typename U = T, typename... Args>
requires (!std::is_const_v<T> && !std::is_volatile_v<T>)
void replace(T *const where, Args&&... args)
noexcept(std::is_nothrow_constructible_v<U, Args...>)
{
using std::construct_at;
using std::destroy_at;
if constexpr ( std::is_nothrow_constructible_v<U, Args...> )
{
try { destroy_at(where); } catch (...) { CRASH_IN_DEBUG_MODE(); }
construct_at(reinterpret_cast<U*>(where), std::forward<Args>(args)...);
}
#if CAN_USE_STD_RELOCATE
else if constexpr ( true )
{
alignas(T) std::byte elsewhere[sizeof(T)];
T *const elsewhereT = static_cast<T*>(static_cast<void*>(elsewhere));
// Relocation is supported (non-standard extension)
if constexpr ( std::is_nothrow_relocatable_v<T> )
{
std::relocate_at(where, elsewhereT);
try { construct_at(reinterpret_cast<U*>(where),
std::forward<Args>(args)...); }
catch (...)
{
std::relocate_at(elsewhereT, where);
throw;
}
try { destroy_at(elsewhereT); } catch (...) {
CRASH_IN_DEBUG_MODE(); }
}
else if constexpr ( std::is_nothrow_move_constructible_v<T> )
{
// Relocation exists but isn't noexcept — fallback to move
construct_at(elsewhereT, std::move(*where));
try { destroy_at(where); } catch (...) { CRASH_IN_DEBUG_MODE(); }
try { construct_at(reinterpret_cast<U*>(where),
std::forward<Args>(args)...); }
catch (...)
{
construct_at(where, std::move(*elsewhereT));
try { destroy_at(elsewhereT); } catch (...) {
CRASH_IN_DEBUG_MODE(); }
throw;
}
try { destroy_at(elsewhereT); } catch (...) {
CRASH_IN_DEBUG_MODE(); }
}
else
{
static_assert( false == requires { typename
T::tag_never_retain; } );
using std::memcpy;
std::byte elsewhere[sizeof(T)];
memcpy(elsewhere, where, sizeof *elsewhere);
try { construct_at(reinterpret_cast<U*>(where),
std::forward<Args>(args)...); }
catch (...)
{
memcpy(where, elsewhere, sizeof *where);
throw;
}
std::swap_ranges(
elsewhere,
elsewhere + sizeof elsewhere,
static_cast<std::byte*>(static_cast<void*>(where))
);
try { destroy_at(where); } catch (...) { CRASH_IN_DEBUG_MODE(); }
memcpy(where, elsewhere, sizeof *where);
}
}
#endif // if CAN_USE_STD_RELOCATE
else if constexpr (std::is_nothrow_move_constructible_v<T>)
{
// No relocation support; fallback to move construction
alignas(T) std::byte elsewhere[sizeof(T)];
T *const elsewhereT = static_cast<T*>(static_cast<void*>(elsewhere));
construct_at(elsewhereT, std::move(*where));
try { destroy_at(where); } catch (...) { CRASH_IN_DEBUG_MODE(); }
try { construct_at(reinterpret_cast<U*>(where),
std::forward<Args>(args)...); }
catch (...)
{
construct_at(where, std::move(*elsewhereT));
try { destroy_at(elsewhereT); } catch (...) {
CRASH_IN_DEBUG_MODE(); }
throw;
}
try { destroy_at(elsewhereT); } catch (...) { CRASH_IN_DEBUG_MODE(); }
}
else
{
static_assert( false == requires { typename T::tag_never_retain; } );
using std::memcpy;
std::byte elsewhere[sizeof(T)];
memcpy(elsewhere, where, sizeof *elsewhere);
try { construct_at(reinterpret_cast<U*>(where),
std::forward<Args>(args)...); }
catch (...)
{
memcpy(where, elsewhere, sizeof *where);
throw;
}
std::swap_ranges(
elsewhere,
elsewhere + sizeof elsewhere,
static_cast<std::byte*>(static_cast<void*>(where))
);
try { destroy_at(where); } catch (...) { CRASH_IN_DEBUG_MODE(); }
memcpy(where, elsewhere, sizeof *where);
}
}
#include <string>
int main(void)
{
struct Monkey {
Monkey(void) noexcept(false) = default;
Monkey(Monkey&) = delete;
Monkey(Monkey&&) = delete;
typedef int tag_never_retain2; // try removing the '2' here
};
Monkey var;
replace(&var);
std::string str;
replace(&str);
replace<std::string,Monkey>(&str);
}
Received on 2025-10-28 12:23:02
