I have some code that look like this:
template<typename T>
struct memory_block {
// Very not copiable, this class cannot move
memory_block(memory_block const&) = delete;
memory_block(memory_block const&&) = delete;
memory_block(memory_block&) = delete;
memory_block(memory_block&&) = delete;
memory_block& operator=(memory_block const&) = delete;
memory_block& operator=(memory_block&&) = delete;
// The only constructor construct the `data` member with args
template<typename... Args>
explicit memory_block(Args&&... args) noexcept :
data{std::forward<Args>(args)...} {}
T data;
};
template<typename T>
struct special_block : memory_block<T> {
using memory_block<T>::memory_block;
std::vector<double> special_data;
};
// There is no other inheritance. The hierarchy ends here.
Now I have to store these types into type erased storage. I chose a vector of void*
as my container. I insert pointers of the data
member into the vector:
struct NonTrivial { virtual ~NonTrivial() {} };
// exposed to other code
std::vector<void*> vec;
// My code use dynamic memory instead of static
// Data, but it's simpler to show it that way.
static memory_block<int> data0;
static special_block<NonTrivial> data1;
void add_stuff_into_vec() {
// Add pointer to `data` member to the vector.
vec.emplace_back(&(data0->data));
vec.emplace_back(&(data1->data));
}
Then later in the code, I access the data:
// Yay everything is fine, I cast the void* to it original type
int* data1 = static_cast<int*>(vec[0]);
NonTrivial* data1 = static_cast<NonTrivial*>(vec[1]);
The problem is that I want to access special_data
in the non-trivial case:
// Pretty sure this cast is valid! (famous last words)
std::vector<double>* special = static_cast<special_block<NonTrivial>*>(
static_cast<memory_block<NonTrivial>*>(vec[1]) // (1)
);
So now, the question
The problem arise at line (1)
: I have a pointer to data
(of type NonTrivial
), which is a member of memory_block<NonTrivial>
. I know that the void*
will always point to the first data member of a memory_block<T>
.
So is casting a void*
to the first member of a class into the class safe? If not, is there another way to do it? If it can make things simpler, I can get rid of the inheritance.
Also, I have no problem using std::aligned_storage
in this case. If that can solve the problem, I'll use that.
I hoped standard layout would help me in this case, but my static assert seem to fail.
My static assert:
static_assert(
std::is_standard_layout<special_block<NonTrivial>>::value,
"Not standard layout don't assume anything about the layout"
);
A class declaration can contain static object of self type, it can also have pointer to self type, but it cannot have a non-static object of self type.
The pointer to member operators . * and ->* are used to bind a pointer to a member of a specific class object. Because the precedence of () (function call operator) is higher than . * and ->* , you must use parentheses to call the function pointed to by ptf .
Overview. A pointer is nothing but a variable that can hold an address of a memory location. The type of pointer is determined by the type of content it holds. This means that a pointer that is supposed to hold the address of an integer type variable must be declared as an integer type of pointer.
As long as memory_block<T>
is a standard-layout type [class.prop]/3, the address of a memory_block<T>
and the address of its first member data
are pointer interconvertible [basic.compound]/4.3. If this is the case, the standard guarantees that you can reinterpret_cast
to get a pointer to one from a pointer to the other. As soon as you don't have a standard-layout type, there is no such guarantee.
For your particular case, memory_block<T>
will be standard-layout as long as T
is standard-layout. Your special_block
will never be standard layout because it contains an std::vector
(as also pointed out by @NathanOliver in his comment below), which is not guaranteed to be standard layout. In your case, since you just insert a pointer to the data
member of the memory_block<T>
subobject of your special_block<T>
, you could still make that work as long as T
is standard-layout if you reinterpret_cast
your void*
back to memory_block<T>*
and then static_cast
that to special_block<T>*
(assuming that you know for sure that the dynamic type of the complete object is actually special_block<T>
). Unfortunately, as soon as NonTrivial
enters the picture, all bets are off because NonTrivial
has a virtual method and, thus, is not standard layout which also means that memory_block<NonTrivial>
will not be standard layout…
One thing you could do is, e.g., have just a buffer to provide storage for a T
in your memory_block
and then construct the actual T
inside the storage of data
via placement new. for example:
#include <utility>
#include <new>
template <typename T>
struct memory_block
{
alignas(T) char data[sizeof(T)];
template <typename... Args>
explicit memory_block(Args&&... args) noexcept(noexcept(new (data) T(std::forward<Args>(args)...)))
{
new (data) T(std::forward<Args>(args)...);
}
~memory_block()
{
std::launder(reinterpret_cast<T*>(data))->~T();
}
…
};
That way memory_block<T>
will always be standard-layout…
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