Skip to content

Instantly share code, notes, and snippets.

@alipha
Created April 15, 2020 17:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alipha/736d3bb16dbb1d59c8d6ce077da0ffc7 to your computer and use it in GitHub Desktop.
Save alipha/736d3bb16dbb1d59c8d6ce077da0ffc7 to your computer and use it in GitHub Desktop.
A class that can store different derived classes with no dynamic allocation and provides value semantics
#ifndef LIPH_POLY_OBJ_H
#define LIPH_POLY_OBJ_H
#include <cstddef>
#include <new>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
namespace liph {
enum class copyable { NO, YES_EXCEPT, YES_NOEXCEPT };
enum class movable { NO, YES_EXCEPT, YES_NOEXCEPT };
template<typename T> struct as_type {};
template<typename... T>
constexpr std::size_t max_sizeof() { return std::max({sizeof(T)...}); }
namespace detail {
template<typename Base, std::size_t MaxSize, copyable Copyable, movable Movable>
struct poly_obj_storage {
enum class action { COPY, MOVE };
using value_type = Base;
static constexpr std::size_t max_size = MaxSize;
static constexpr copyable is_copyable = Copyable;
static constexpr movable is_movable = Movable;
static Base *null_action(unsigned char *, Base *, action) { return nullptr; }
template<typename Derived>
static Base *do_action_impl(unsigned char *dest, Base *src, action a) {
if constexpr(std::is_copy_constructible_v<Derived>) {
if(a == action::COPY) return new (dest) Derived(*static_cast<Derived*>(src));
}
if constexpr(std::is_move_constructible_v<Derived>) {
if(a == action::MOVE) return new (dest) Derived(std::forward<Derived>(static_cast<Derived&>(*src)));
}
throw std::logic_error("Action " + std::string(a == action::MOVE ? "MOVE" : "COPY") + " performed on object which doesn't support it. Shouldn't be possible to happen.");
}
poly_obj_storage() noexcept : do_action(null_action), ptr(nullptr) {}
template<typename Derived, typename... Args>
poly_obj_storage(as_type<Derived>, Args&&... args) : do_action(do_action_impl<Derived>), ptr(new (buffer) Derived(std::forward<Args>(args)...)) {
static_assert(sizeof(Derived) <= MaxSize, "Derived type is larger than will fit into poly_obj");
}
/*
template<typename OtherPolyObj>
void assert_compatible() {
static_assert(MaxSize >= OtherPolyObj::max_size, "Cannot copy a larger poly_obj into a smaller poly_obj");
static_assert(static_cast<int>(Copyable) <= static_cast<int>(OtherPolyObj::is_copyable), "Target poly_obj cannot have less-restrictive copy semantics");
static_assert(static_cast<int>(Movable) <= static_cast<int>(OtherPolyObj::is_movable), "Target poly_obj cannot have less-restrictive move semantics");
}
template<std::size_t OtherSize, copyable OtherCopyable, movable OtherMovable>
poly_obj_storage(const poly_obj_storage<Base, OtherSize, OtherCopyable, OtherMovable> &other) : do_action(other.do_action), ptr(do_action(buffer, other.ptr, action::COPY) {
assert_compatible<decltype(other)>();
}
template<std::size_t OtherSize, copyable OtherCopyable, movable OtherMovable>
poly_obj_storage(poly_obj_storage<Base, OtherSize, OtherCopyable, OtherMovable> &&other) : do_action(other.do_action), ptr(do_action(buffer, other.ptr, action::MOVE) {
assert_compatible<decltype(other)>();
}
template<std::size_t OtherSize, copyable OtherCopyable, movable OtherMovable>
poly_obj_storage &operator=(poly_obj_storage<Base, OtherSize, OtherCopyable, OtherMovable> &&other) {
assert_compatible<decltype(other)>();
}
*/
poly_obj_storage(const poly_obj_storage &other) noexcept(Copyable == copyable::YES_NOEXCEPT) : do_action(other.do_action), ptr(do_action(buffer, other.ptr, action::COPY)) {}
poly_obj_storage(poly_obj_storage &&other) noexcept(Movable == movable::YES_NOEXCEPT) : do_action(other.do_action), ptr(do_action(buffer, other.ptr, action::MOVE)) {}
poly_obj_storage &operator=(const poly_obj_storage &other) noexcept(Copyable == copyable::YES_NOEXCEPT && noexcept(this->ptr->~Base())) {
do_action = other.do_action;
if constexpr(Copyable == copyable::YES_NOEXCEPT || Movable != movable::YES_NOEXCEPT) {
if(ptr)
ptr->~Base();
ptr = do_action(buffer, other.ptr, action::COPY);
} else {
alignas(std::max_align_t) unsigned char temp_buf[MaxSize];
Base *temp_ptr = do_action(temp_buf, other.ptr, action::COPY);
if(ptr)
ptr->~Base();
ptr = do_action(buffer, temp_ptr, action::MOVE);
if(temp_ptr)
temp_ptr->~Base();
}
return *this;
}
poly_obj_storage &operator=(poly_obj_storage &&other) noexcept((Movable == movable::YES_NOEXCEPT || Copyable == copyable::YES_NOEXCEPT) && noexcept(this->ptr->~Base())) {
do_action = other.do_action;
if(ptr)
ptr->~Base();
ptr = do_action(buffer, other.ptr, Movable != movable::YES_NOEXCEPT && Copyable == copyable::YES_NOEXCEPT ? action::COPY : action::MOVE);
return *this;
}
~poly_obj_storage() noexcept(noexcept(this->ptr->~Base())) { if(ptr) ptr->~Base(); }
alignas(std::max_align_t) unsigned char buffer[MaxSize];
Base *(*do_action)(unsigned char *, Base *, action);
Base *ptr;
};
template<typename Base, std::size_t MaxSize>
struct poly_obj_storage<Base, MaxSize, copyable::NO, movable::NO> {
poly_obj_storage() noexcept : ptr(nullptr) {}
template<typename Derived, typename... Args>
poly_obj_storage(as_type<Derived>, Args&&... args) : ptr(new (buffer) Derived(std::forward<Args>(args)...)) {
static_assert(sizeof(Derived) <= MaxSize, "Derived type is larger than will fit into poly_obj");
}
~poly_obj_storage() noexcept(noexcept(ptr->~Base())) { if(ptr) ptr->~Base(); }
alignas(std::max_align_t) unsigned char buffer[MaxSize];
Base *ptr;
};
template<typename Base, std::size_t MaxSize, copyable Copyable, movable Movable>
class poly_obj_ctor : public poly_obj_storage<Base, MaxSize, Copyable, Movable> {
public:
using poly_obj_storage<Base, MaxSize, Copyable, Movable>::poly_obj_storage;
poly_obj_ctor(const poly_obj_ctor &) = default;
poly_obj_ctor(poly_obj_ctor &&) = default;
poly_obj_ctor &operator=(const poly_obj_ctor &) = default;
poly_obj_ctor &operator=(poly_obj_ctor &&) = default;
};
template<typename Base, std::size_t MaxSize, copyable Copyable>
class poly_obj_ctor<Base, MaxSize, Copyable, movable::NO> : public poly_obj_storage<Base, MaxSize, Copyable, movable::NO> {
public:
using poly_obj_storage<Base, MaxSize, Copyable, movable::NO>::poly_obj_storage;
poly_obj_ctor(const poly_obj_ctor &) = default;
poly_obj_ctor(poly_obj_ctor &&) = delete;
poly_obj_ctor &operator=(const poly_obj_ctor &) = default;
poly_obj_ctor &operator=(poly_obj_ctor &&) = delete;
};
template<typename Base, std::size_t MaxSize, movable Movable>
class poly_obj_ctor<Base, MaxSize, copyable::NO, Movable> : public poly_obj_storage<Base, MaxSize, copyable::NO, Movable> {
public:
using poly_obj_storage<Base, MaxSize, copyable::NO, Movable>::poly_obj_storage;
poly_obj_ctor(const poly_obj_ctor &) = delete;
poly_obj_ctor(poly_obj_ctor &&) = default;
poly_obj_ctor &operator=(const poly_obj_ctor &) = delete;
poly_obj_ctor &operator=(poly_obj_ctor &&) = default;
};
template<typename Base, std::size_t MaxSize>
class poly_obj_ctor<Base, MaxSize, copyable::NO, movable::NO> : public poly_obj_storage<Base, MaxSize, copyable::NO, movable::NO> {
public:
using poly_obj_storage<Base, MaxSize, copyable::NO, movable::NO>::poly_obj_storage;
poly_obj_ctor(const poly_obj_ctor &) = delete;
poly_obj_ctor(poly_obj_ctor &&) = delete;
poly_obj_ctor &operator=(const poly_obj_ctor &) = delete;
poly_obj_ctor &operator=(poly_obj_ctor &&) = delete;
};
} // namespace detail
template<typename Base, std::size_t MaxSize, copyable Copyable, movable Movable>
class poly_obj : private detail::poly_obj_ctor<Base, MaxSize, Copyable, Movable> {
public:
using detail::poly_obj_ctor<Base, MaxSize, Copyable, Movable>::poly_obj_ctor;
Base *get() noexcept { return this->ptr; }
const Base *get() const noexcept { return this->ptr; }
Base &operator*() noexcept { return *this->ptr; }
const Base &operator*() const noexcept { return *this->ptr; }
Base *operator->() noexcept { return this->ptr; }
const Base *operator->() const noexcept { return this->ptr; }
};
/*
template<typename Base, typename Derived, std::size_t MaxSize, copyable Copyable = detail::is_copyable<Derived>::value, movable Movable = detail::is_movable<Derived>::value, typename... Args>
poly_obj<Base, MaxSize, Copyable, Movable> make_poly_obj(Args&&... args) {
return poly_obj<Base, MaxSize, Copyable, Movable>(as_type<Derived>(), std::forward<Args>(args)...);
}
*/
} // namespace liph
#endif
#include <iostream>
struct shape {
virtual ~shape() = default;
virtual void print(std::ostream &os) const = 0;
};
struct square : shape {
square(int size) : size(size) {}
void print(std::ostream &os) const override { os << "square: size = " << size; }
int size;
};
struct rectangle : shape {
rectangle(int width, int height) : width(width), height(height) {}
void print(std::ostream &os) const override { os << "rectangle: width = " << width << ", height = " << height; }
int width;
int height;
};
int main() {
using shape_obj = liph::poly_obj<shape, liph::max_sizeof(rectangle), liph::copyable::YES_NOEXCEPT, liph::movable::YES_NOEXCEPT>;
shape_obj s(liph::as_type<square>(), 2);
shape_obj r(liph::as_type<rectangle>(), 5, 8);
s->print(std::cout);
r->print(std::cout);
s = r;
s->print(std::cout);
r->print(std::cout);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment