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: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);
}

Received on 2025-10-28 12:23:02