Let's say I have two unrelated types that are exactly the same size.
struct Foo { ... };
struct Bar { ... };
And now, let's say that I have a tree of Foo
that I would like to convert to a tree of Bar
.
template <typename Leaf>
struct Tree
{
Leaf data;
std::unique_ptr<Tree> lhs;
std::unique_ptr<Tree> rhs;
};
Tree<Foo> footree = ...;
Tree<Bar> bartree = convert(std::move(footree)); // <--
How could I perform this conversion while reusing the heap-allocated memory of footree
? In other words, how can I (safely) convert a Tree<Foo>
to a Tree<Bar>
with no extra memory allocations?
This is a simplified example of what I'm actually trying to do. In reality Foo
and Bar
are recursive variants with differing numbers of alternative types.
For raw pointers it isn't difficult. One simply needs to trigger destructor of Foo
manually and apply constructor manually via placement new. The enable_if
is SFINAE that ensures that sizes and alignment match.
#include <iostream>
#include <vector>
template<typename U, typename V, typename... Args,
std::enable_if_t<sizeof(U) == sizeof(V) && alignof(U) == alignof(V), int> = 0>
U* repurpose_raw_ptr(V* ptr, Args&&... args)
{
ptr->~V();
return new ((void*)ptr) U(std::forward<Args>(args)...);
}
int main()
{
std::vector<int>* X = new std::vector<int>({1,2,3,4,5});
std::vector<double>* Y =
repurpose_raw_ptr<std::vector<double>>(X,
std::initializer_list<double>{1.,2.,3.,4.,5.});
std::cout << Y->at(0) << " " << Y->at(4);
delete Y;
return 0;
}
You can apply the function to "convert" from std::unique_ptr<Foo>
to std::unique_ptr<Bar>
by using the release()
method of unique pointer where it releases ownership of the object. Something like:
std::unique_ptr<Bar> X = ...;
std::unique_ptr<Foo> Y = std::unique_ptr<Foo>(repurpose_raw_ptr<Bar>(X.release(),...));
Here it assumes delete Foo
and delete Bar
is same as destruction of Foo/Bar + dealloc of a raw pointer which might not always be the case I believe.
In general, I do not recommend doing something like this as it is hard to ensure that structures have same size/alignment in a cross-platform environment.
Edit: Formerly, missed that you wanted to convert Foo
to Bar
in some way. You'll have to temporarily copy/move instance Foo
elsewhere and then trigger trigger the memory repurposing.