Let S
be a struct type which contains a character array data
which has the maximum alignment and a fixed size. The idea is that S
is able to store any object of type T
whose size does not exceed the limit and which is trivially-copy-constructible and trivially-destructible.
static constexpr std::size_t MaxSize = 16;
struct S {
alignas(alignof(std::max_align_t)) char data[MaxSize];
};
Placement-new is used to construct an object of type T
into the character array of a new S
object. This object is then copied any number of times, including being returned and passed by value.
template <typename T>
S wrap(T t) {
static_assert(sizeof(T) <= MaxSize, "");
static_assert(std::is_trivially_copy_constructible_v<T>, "");
static_assert(std::is_trivially_destructible_v<T>, "");
S s;
new(reinterpret_cast<T *>(s.data)) T(t);
return s;
}
Later given a copy of this S
value, reinterpret_cast
is used to obtain T*
from the pointer to the start of the character array, and then the T
object is accessed in some way. The T
type is the same as when the value was created.
void access(S s) {
T *t = reinterpret_cast<T *>(s.data);
t->print();
}
I would like to know if there is any undefined behavior involved in this scheme and how it would be solved. For instance, I am worried about:
- Is there a problem with "reusing object storage", i.e. the problem that
std::launder
is designed to solve? I am not sure if it is valid to accessdata
as a character array after constructing an instance ofT
there. Would I needstd::launder
in the place where the value is accessed, and why? - Is there a problem in the generated copy constructor of
S
which copies all the bytes indata
, because some bytes might not have been initialized? I am worried both about bytes beyondsizeof(T)
as well as possibly uninitialized bytes within theT
object (e.g. padding).
My use case for this is implementation of a very lightweight polymorphic function wrapper which is able to be used with any callable satisfying those requirements that I have listed for T
.