After P0593R6 ('Implicit creation of objects for low-level object manipulation') was accepted in C++20, C++23 will get std::start_lifetime_as() which 'completes the functionality proposed in [P0593R6]' (cf. P2590R2, P2679R2 and the cppreference C++ 23 feature testing page).
How could a reference implementation of std::start_lifetime_as() look like?
Would something like this be sufficient, or is there more to it?
#include <cstddef>
#include <new>
template<class T>
T* start_lifetime_as(void* p) noexcept
{
new (p) std::byte[sizeof(T)];
return static_cast<T*>(p);
}
std::start_lifetime_as cannot be implemented fully by hand, because it has the special property that it doesn't access the storage. Any implementation we can provide ourselves will have to access the storage in theory, even if this can be optimized out in practice.
However, disregarding this detail, we can implement it as follows:
std::memmoveSince C++20, std::memmove and std::memcpy are "magic" in the sense that they implicitly begin the lifetimes of objects at the destination.
std::memmove can have the same source and destination, so we can hijack its magic properties and implement std::start_lifetime_as easily:
template<class T>
requires (std::is_trivially_copyable_v<T> && std::is_implicit_lifetime_v<T>)
T* start_lifetime_as(void* p) noexcept
{
return std::launder(static_cast<T*>(std::memmove(p, p, sizeof(T))));
}
The reason why this works is that:
Both functions [
std::memcpyandstd::memmove] implicitly create objects in the destination region of storage immediately prior to copying the sequence of characters to the destination.
- [cstring.syn] p3
std::memmove turns the memory region [p, p + sizeof(T)) into one where an object is implicitly created. You may ask what the type of that object is:
For each operation that is specified as implicitly creating objects, that operation implicitly creates and starts the lifetime of zero or more objects of implicit-lifetime types in its specified region of storage if doing so would result in the program having defined behavior.
- [intro.object] p10
Without std::launder, it may be possible that objects of a type other than T are created in this memory region. However, std::launder has the precondition that there must be a T at p (see [ptr.launder] p2), so the compiler must create T there to satisfy the former paragraph.
std::start_lifetime_as is implementable even without std::memmove being "magic", and P0593R6 explains exactly how to do it.
The explanation in that paper predates std::memmove being given magic properties, which is why it suggests a more complicated implementation:
If the destination type is a trivially-copyable implicit-lifetime type, this can be accomplished by copying the storage elsewhere, using placement new of an array of byte-like type, and copying the storage back to its original location, then using
std::launderto acquire a pointer to the newly-created object.
- §3.8 Direct object creation
template<class T>
requires (std::is_trivially_copyable_v<T> && std::is_implicit_lifetime_v<T>)
T* start_lifetime_as(void* p) noexcept
{
// 1. Copy the storage elsewhere.
std::byte backup[sizeof(T)];
std::memcpy(backup, p, sizeof(T));
// 2. Use placement new of an array of byte-like type
// according to [intro.objec] p13, this implicitly begins the lifetime
// of an object within the byte storage.
// However, it also turns the memory at p indeterminate.
new (p) std::byte[sizeof(T)];
// 3. Copy the storage back to its original location.
// This turns the object representation determinate again,
// while keeping the implicit object creation at p.
std::memcpy(p, backup, sizeof(T));
// 4. Return a laundered pointer.
// Because T being at the address that p represents is a
// precondition of std::launder, this forces the implicit
// object created via placement new to be of type T.
return std::launder(static_cast<T*>(p));
}
Note 1: Your implementation has the problem that the memory turns indeterminate when you do placement new of a std::byte[], so the object representation won't be preserved.
Note 2: The C++17 version isn't complete, because it doesn't allow for starting the lifetime of volatile types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With