I've searched StackOverflow, but I couldn't find a question that directly addresses this issue.
First some context: I'm trying to implement an Either
type in C++ that can handle polymorphic data, much like you can throw a std::runtime_error
without the new
-keyword. Everything works fine with primitive types, PODs and references, but given that we cannot know the size of a polymorphic data structure upfront, things get more difficult. I then thought about copying the structure to a raw buffer on the heap so that I can pass it around as if it was on the stack.
Example of an Either<L, R>
-type:
Either<std::runtime_error, int> doSomeStuff() {
if (err) {
return left(std::runtime_error("Not right!"));
}
return right(42);
}
I experimented with things like std::memcpy(buf, reinterpret_cast<char*>(static_cast<T*>(&value)), sizeof(T))
, but I keep getting SIGSEGV errors. Is this because, as I suspect, polymorphic structures hold extra bookkeeping that becomes corrupt when copying? Is there a way to hold an arbitrary polymorphic structure T
on the heap so I can pass it as if it were a normal stack-allocated object? Or is such a thing "undefined" in today's C++ standards?
Update: here's the code I have so far. It's not pretty, but it's the best I've got.
struct ConstBoxRefTag { };
struct BoxMoveTag { };
struct PlainValueTag { };
// struct BoxValueTag { };
template<typename T>
struct GetTag { using type = PlainValueTag; };
template<typename T>
struct GetTag<const Box<T>&> { using type = ConstBoxRefTag; };
template<typename T>
struct GetTag<Box<T>&&> { using type = BoxMoveTag; };
template<typename T>
struct GetTag<Box<T>> { using type = ConstBoxRefTag; };
template<typename T>
class Box<T, typename std::enable_if<std::is_polymorphic<T>::value>::type> {
void* buf;
size_t sz;
template<typename R, typename Enabler>
friend class Box;
public:
using Type = T;
template<typename R>
Box(R val): Box(typename box::GetTag<R>::type {}, val) {}
template<typename R>
Box(ConstBoxRefTag, R oth): buf(std::malloc(oth.sz)), sz(oth.sz) {
std::memcpy(buf, oth.buf, oth.sz);
}
template<typename R>
Box(BoxMoveTag, R oth): buf(std::move(oth.buf)), sz(std::move(oth.sz)) {
oth.buf = nullptr;
};
template<typename R>
Box(PlainValueTag, R val): buf(std::malloc(sizeof(R))), sz(sizeof(R)) {
std::memcpy(buf, reinterpret_cast<void*>(static_cast<T*>(&val)), sizeof(R));
}
template<typename R>
R as() const {
static_assert(std::is_base_of<T, R>::value, "Class is not a subtype of base class");
return *static_cast<const R*>(reinterpret_cast<const T*>(&buf));
}
T& reference() {
return *reinterpret_cast<T*>(&buf);
}
const T& reference() const {
return *static_cast<T*>(&buf);
}
~Box() {
if (buf != nullptr) {
reference().~T();
std::free(buf);
}
}
};
Indeed, the standard recently added a concept "trivially copyable", such that using memcpy
on an object which isn't trivially copyable doesn't result in a valid object. Before "trivially copyable" was introduced, this was controlled by POD-ness.
To make a copy of a C++ object, you need to call its copy constructor. There's no standard polymorphic way of doing that, but some class hierarchies choose to include a virtual clone()
function (or similar) which would meet your need.
Your other option is to find the way to avoid the copy entirely.