Date: Tue, 28 Oct 2025 12:16:48 +0000
On Tue, Oct 28, 2025 at 11:46 AM Nikl Kelbon wrote
>
> No one really has correct code with std::varant right now, because no one handles valueless_by_exception, but its possible to get rid of it.
>
> 0. backup memory of old value
> 1. construct new value ON TOP of memory of old value
> On exception just memswap backup and new value (new value not constructed)
>
> On success
> 2. memswap 2 buffers - with new value and backup
> 3. call destructor for old value
> 4. replace buffer without value with new value
Here's code I shared earlier in the week on this mailing list:
https://godbolt.org/z/nbeKMKe1o
template<typename 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<T, Args...>)
{
using std::construct_at;
using std::destroy_at;
if constexpr ( std::is_nothrow_constructible_v<T, Args...> )
{
try { destroy_at(where); } catch (...) { CRASH_IN_DEBUG_MODE(); }
construct_at(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(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(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(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(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(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);
}
}
>
> No one really has correct code with std::varant right now, because no one handles valueless_by_exception, but its possible to get rid of it.
>
> 0. backup memory of old value
> 1. construct new value ON TOP of memory of old value
> On exception just memswap backup and new value (new value not constructed)
>
> On success
> 2. memswap 2 buffers - with new value and backup
> 3. call destructor for old value
> 4. replace buffer without value with new value
Here's code I shared earlier in the week on this mailing list:
https://godbolt.org/z/nbeKMKe1o
template<typename 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<T, Args...>)
{
using std::construct_at;
using std::destroy_at;
if constexpr ( std::is_nothrow_constructible_v<T, Args...> )
{
try { destroy_at(where); } catch (...) { CRASH_IN_DEBUG_MODE(); }
construct_at(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(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(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(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(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(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);
}
}
Received on 2025-10-28 12:16:33
