Date: Tue, 21 Oct 2025 18:04:59 +0200
Interesting problem statement. This has effects on lifetime and identity.
During the call of the constructor the (possibly) to be replaced object would obviously be unavailable. The pointers are invalid and the member variables are not in the possibly marked memory. There is a reason for the object not being movable.
This can lead to three issues:
- The owner of the object knows, what they did to the object. But the object itself does not. And during that time code is executed (the constructor of the new object). That code can indirectly access the object (e.g. over some object registry, e.g. all old objects are notified about a new construction)
- For a short time the newly constructed object and the old object are at the same position in memory. That can lead to identity problems. (The question is, whether the new object can rely on its identity, before the constructor has finished). If we keep the example of a registry, it would get a second entry for the same location.
- When the object is finally destructed (after a successful constructor) the immovable object is at a different location in memory during the destructor call
-----Ursprüngliche Nachricht-----
Von:Frederick Virchanza Gotham via Std-Proposals <std-proposals_at_[hidden]>
Gesendet:Di 21.10.2025 18:03
Betreff:[std-proposals] Replace an object -- but retain old object if new object fails to construct
An:std-proposals <std-proposals_at_[hidden]>;
CC:Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>;
There is more than one scenario in which I would want to replace an
old object with a new object, but for this use case I'll focus on
std::optional.
Let's say we have a variable of type std::optional<std::mutex>. Yes
you guessed right, I picked my favourite class 'std::mutex' because
it's both unmovable and uncopyable.
So let's start off by creating our variable:
std::optional<std::mutex> om;
om.emplace();
Now . . . . later on in the program, we want to replace the contained
object -- but only if the construction of the new object is
successful. If the construction of the new object fails, we want to
retain the old object. So here's the strategy:
(Step 1) Copy the bytes of the current object into a temporary buffer
on the stack
(Step 2) Construct the new object at the original location of the old
object (i.e. overwrite the old object)
(Step 3) If constructor throws an exception, move the old object's
bytes back from the temporary buffer (i.e. restore the old object)
(Step 4) Otherwise, if construction succeeds, swap the two buffers,
call the destructor to destroy the old object, then swap the two
buffers again
So the implementation of a free-standing function would look like this:
template<StdOptional Opt, typename... Params>
constexpr bool emplace_or_retain(Opt &&opt, Params&&... args) noexcept
{
typedef typename std::remove_cvref_t<Opt>::value_type T;
if ( false == opt.has_value() )
{
try
{
opt.emplace( std::forward<Params>(args)... );
return true;
}
catch(...){}
return false;
}
// (1) Copy old object's bytes to temporary buffer
T *const p = std::addressof( opt.value() );
std::byte backup[sizeof(T)];
std::memcpy(backup, p, sizeof backup);
// (2) Try to construct the new object in-place (overwriting
the old one)
try
{
std::construct_at( p, std::forward<Params>(args)... );
// (3) Construction succeeded, so swap the two buffers,
// destroy the old object, and then move the new object back
std::swap_ranges( backup,
backup + sizeof backup,
static_cast<std::byte*>(static_cast<void*>(p)) );
try { std::destroy_at(p); } catch(...){}
std::memcpy( p, backup, sizeof backup );
return true;
}
catch (...){}
// (4) Construction failed: restore old object
std::memcpy( p, backup, sizeof backup );
return false;
}
And here it is tested up on GodBolt:
https://godbolt.org/z/cxf3Wqdhe
Now see here's the thing . . . I'm probably more of a firmware
engineer than a software engineer, and so I have a little difficulty
maintaining respect for the "object model" since I deal so much with
copper and voltages and CPU registers, but I can at least concede that
my above code absolutely decimates the object model in the most
horrendous and unspeakable manner. Also I'm pretty sure I'm not
allowed to 'memcpy' a class like 'std::mutex' either.
So anyway, to get around the whole object model boggle and memcpy's
class bytes, perhaps could the standard library be given a new
function called something like "emplace_or_retain", and so then
compiler vendors could take my above code and put it in their header
files and then put some fine print with it saying "The compiler
maintains the sanctity of the C++ object model when compiling this
code".
--
Std-Proposals mailing list
Std-Proposals_at_[hidden]
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
Received on 2025-10-21 16:17:57
