cppreference's example of std::allocator contains this code (shortened for simplicity):
// default allocator for ints
std::allocator<int> alloc1;
using traits_t1 = std::allocator_traits<decltype(alloc1)>; // The matching trait
p1 = traits_t1::allocate(alloc1, 1);
traits_t1::construct(alloc1, p1, 7); // construct the int
std::cout << *p1 << '\n';
Rather straight-forward as far as allocators are concerned. However, what wording in the standard guarantess that p1
actually points to the new object?
According to cppreference documentation on std::allocate and [allocator.members],
the default allocator's allocate()
function
creates an array of type T[n] in the storage and starts its lifetime, but does not start lifetime of any of its elements.
and returns
[a pointer] to the first element of an array of n objects of type T whose elements have not been constructed yet.
Afaik, the array creation wording was added to the standard so that pointer-arithmetic on the pointer is valid. In any case, this means that the returned pointer points the first element of the T[]
and the lifetime of this first element was not started.
construct()
then creates an object at this location, however, it does not return a pointer to this object. The only pointer we have is still the one allocate
returned.
Usually when an object is placed in the location of an expired object, it can "transparently replace" the old one under the conditions laid out in [basic.life]p8: (emphasis mine)
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object [...] will automatically refer to the new object and, once the lifetime of the new object has started [...]
The lifetime of this array element was never started so it cannot have ended, so this should not apply. How else can it then be guaranteed that accessing the newly constructed object is well-defined? Is std::launder
supposed to be used after every construct
call?
Please keep in mind that this is a [language-lawyer]-tagged question. It's not about whether this code practically works but about the "laws" of the standard.
launder
to me. – Kamenew
is valid? Because it seems that's exactly whatallocator::construct
uses. – Endocrinotherapylaunder
unnecessary. – Horseyconstruct
throws that pointer away and doesn't return anything. So with plain placement-new, the problem can be side-stepped by using the returned pointer. aditionally, in the situations where placement-new is used, basic.life/8 usually applies because the overwritten object was once within its lifetime. only allocator seems to be an exception in that it can create an array without starting the lifetime of this array's members. – Horseyconstruct
will however create a _new object in its place and start the lifetime of that new object, not the old one. "OK, there wasn't an object there before": The new object is reusing the storage of the old one that never had its lifetime started. – Hardemanallocate
would have to be invalid and could never automatically refer to another object). – Hardemanlanguage-lawyer
tag? – Endocrinotherapystd::allocator::allocate
. Any implicit object creation can create and start the lifetime of an array object with any non-implicit lifetime element type, but won't start the lifetime of its elements. You can make the same argument there. – Hardemantraits_t1::construct(alloc1, p1, 7);
, the object (int) created satisfies[intro.object]/9
. As a consequence, it may have the same address asp1
. "Two objects with overlapping lifetimes [here: the p1 array object and the created int] may have the same address if one is nested within the other" – Mccueconstruct_at
from C++20 does return the pointer to the newly created object. – Mccueallocator_traits::construct
calls thisconstruct_at
, afaik, since in C++20 the defaultstd::allocator
does not implement a memberconstruct
function anymore, so theallocator_traits::construct
falls back toconstruct_at
, however, it does not in turn pass that pointer on to the caller. – Horseyallocator_traits::construct
is quite problematic. – Mccueunion U { int i; double d; } u {}; u.d = 0;
willd
name the new object, since originalu.d.
has never been alive? (In the union case, itsa bit unclear ifd
is a «name») – Millpond