C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Way to get rid of valueless_by_exception in std::variant

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
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);
    }
}

Received on 2025-10-28 12:16:33