This is very similar to Correct usage of placement-new and explicit destructor call but tighter in scope:
If I have a type, S
, for which std::is_nothrow_default_constructible_v<S>
and std::is_nothrow_destructible_v<S>
and not std::has_virtual_destructor_v<S>
and not std::is_polymorphic_v<S>
, is it defined behavior to call this function?:
template <typename T>
void reconstruct(T& x) noexcept {
// C 20: requires instead of static_assert:
static_assert(std::is_nothrow_default_constructible_v<T>);
static_assert(std::is_nothrow_destructible_v<T>);
static_assert(!std::has_virtual_destructor_v<T>);
static_assert(!std::is_polymorphic_v<T>);
x.~T();
::new (&x) T{};
}
What if there are existing pointers or references to, as in
int main() {
S s;
s.x = 42;
const S& sref = s;
reconstruct(s);
return sref.x; // Is this UB because the original S sref references no longer exists?
}
My reason to ask this is that std::once_flag
has no reset mechanism. I know why it generally can't and it would be easy to misuse, however, there are cases where I'd like to do a thread-unsafe reset, and I think this reconstruction pattern would give me what I want provided this reconstruction is defined behavior.
https://godbolt.org/z/de5znWdYT
CodePudding user response:
Unfortunately, sometimes it is defined behavior, and other times you have to run the pointer through std::launder
. There are a number of cases where the compiler may assume that the object hasn't changed, particularly if there are references or const
fields in struct S
. More information is available in the cppreference section on storage reuse.
In the particular code that you show, you will run into problems if S
is a base class of the complete object, because the compiler could, for example, be caching to wrong vtable somewhere.