Consider the following, simplified and incomplete, implementation of a fixed-sized vector:
template<typename T>
class Vec {
T *start, *end;
public:
T& operator[](ssize_t idx) { return start[idx]; }
void pop() {
end--;
end->~T();
}
template<typename... U>
void push(U... args) {
new (end) T { std::forward<U>(args)... };
end++;
}
};
Now consider the following T:
struct T {
const int i;
};
And the following use case:
Vec<T> v;
v.push(1);
std::cout << v[0].i;
v.pop();
v.push(2);
std::cout << v[0].i;
The index operator uses the start
pointer to access the object. The object at that point was destroyed by pop
and another object was created in its storage location by push(2)
. If I read the documentation surrounding std::launder correctly, this means that the behavior of v[0]
in the line below is undefined.
How is std::launder supposed to be used to correct this code? Do we have to launder start and end each time placement new is used? Current implementations of the stdlib seem to be using code similar to the one posted above. Is the behavior of these implementations undefined?
How is std::launder
supposed to be used to correct this code? Do we have to launder start and end each time placement new is used?
From P0532R0, you could avoid needing to call launder()
if the return value of placement new is assigned to end
. You would not need to change your start pointer unless the vector was empty since the object currently pointed to by start
would still have an active lifetime with the code you provided.
The same paper indicates that launder()
is a no-op unless the object lifetime has ended and has been replaced with a new object, so using launder()
will not incur a performance penalty if it is unnecessary:
[...] the type of
std::launder(this)
is equivalent to just this as Richard Smith pointed out: Remember thatlaunder(p)
is a no-op unless p points to an object whose lifetime has ended and where a new object has been created in the same storage.
Current implementations of the stdlib seem to be using code similar to the one posted above. Is the behavior of these implementations undefined?
Yes. P0532R0 also discusses this issue and the content is similar to the discussion in the question's comments: vector
does not use placement new directly, the return value of the placement new call is lost in the chain of function calls to the vector's allocator, and in any event placement new is used element by element so constructing the internal vector machinery cannot use the return value anyway. launder()
appears to be the tool intended to be used here. However, the pointer type specified by the allocator is not required to be a raw pointer type at all and launder()
only works for raw pointers. The current implementation is currently undefined for some types; launder()
does not seem to be the appropriate machinery for solving the generic case for allocator based containers.
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