Created
April 15, 2020 17:39
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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