diff --git a/test/demo/type_erasure_shared_pointer_value_manager.cpp b/test/demo/type_erasure_shared_pointer_value_manager.cpp index a8459db..3b39252 100644 --- a/test/demo/type_erasure_shared_pointer_value_manager.cpp +++ b/test/demo/type_erasure_shared_pointer_value_manager.cpp @@ -3,16 +3,105 @@ #include "zoo/FunctionPolicy.h" #include +#include + +// Primary template: defaults to false +template +struct ExclusiveAwareTrait: std::false_type {}; + +// Specialization: if T has T::ExclusiveAware, this will match and be true +template +struct ExclusiveAwareTrait>: std::true_type {}; + + +struct MyCoolStruct { + struct ExclusiveAware {}; +}; + +static_assert(ExclusiveAwareTrait::value == false); +static_assert(ExclusiveAwareTrait::value); + + +struct StringInputOutput { + struct VTableEntry { + std::string (*str)(const void *); + void (*fromString)(void *, const std::string &); + }; + + template + constexpr static inline VTableEntry Default = { + [](const void *) { return std::string(); }, + [](void *, const std::string &) {} + }; + + template + constexpr static inline VTableEntry Operation = { + [](const void *container) { + auto c = const_cast(container); + auto cvm = static_cast(c); + std::ostringstream oss; + oss << *cvm->value(); + return oss.str(); + }, + [] (void *container, const std::string &str) { + auto c = const_cast(container); + auto cvm = static_cast(c); + + if constexpr (ExclusiveAwareTrait::value) { + if (!cvm->isExclusive()) { + cvm->makeExclusive(); + } + } + + std::istringstream iss{str}; + iss >> *cvm->value(); + }, + }; + + // No extra state/functions needed in the memory layout + template + struct Mixin {}; + + template + struct UserAffordance { + + std::string stringize() const { + auto container = + const_cast(static_cast(this))->container(); + return container->template vTable()->str(container); + } + + auto fromString(const std::string &str) { + auto container = static_cast(this)->container(); + auto vt = container->template vTable(); + vt->fromString(container, str); + } + }; +}; + namespace user { template auto extractSharedPointer(zoo::AnyContainer &a) { using VBuilder = typename Policy::template Builder; - auto downcasted = static_cast(a.container()); - return downcasted->sharedPointer(); + auto valueManager = static_cast(a.container()); + return valueManager->sharedPointer(); +} + +template +auto isExclusive(zoo::AnyContainer &a) { + if constexpr (SharedPointerOptIn::value) { + using VBuilder = typename Policy::template Builder; + auto valueManager = static_cast(a.container()); + return valueManager->isExclusive(); + } + + return true; } + + } using LocalBuffer = void *[4]; @@ -21,26 +110,70 @@ static_assert(sizeof(std::shared_ptr) <= sizeof(LocalBuffer)); using UAny = zoo::AnyContainer< user::SharedPointerPolicy< LocalBuffer, - zoo::Destroy, zoo::Move, zoo::Copy, zoo::RTTI + zoo::Destroy, zoo::Move, zoo::RTTI > >; + TEST_CASE("Shared Pointer Value Manager", "[demo][type-erasure][shared-pointer-policy]") { - UAny uAny{9.9}; - CHECK(9.9 == *uAny.state()); - user::ExplicitDestructor ed; - REQUIRE(nullptr == user::ExplicitDestructor::last); - uAny = ed; - CHECK(nullptr == user::ExplicitDestructor::last); - REQUIRE(typeid(user::ExplicitDestructor) == uAny.type()); - auto spp = user::extractSharedPointer(uAny); - auto sp = *spp; - REQUIRE(2 == sp.use_count()); - CHECK(nullptr == user::ExplicitDestructor::last); - const auto oldAddress = uAny.state(); - REQUIRE(oldAddress == &*sp); - sp.reset(); - REQUIRE(1 == spp->use_count()); - uAny = 5; - REQUIRE(oldAddress == user::ExplicitDestructor::last); + SECTION("shared pointer value management basics") { + UAny uAny{9.9}; + CHECK(9.9 == *uAny.state()); + + REQUIRE(user::isExclusive(uAny)); + + user::ExplicitDestructor ed; + REQUIRE(nullptr == user::ExplicitDestructor::last); + uAny = ed; + CHECK(nullptr == user::ExplicitDestructor::last); + REQUIRE(typeid(user::ExplicitDestructor) == uAny.type()); + auto spp = user::extractSharedPointer(uAny); + auto sp = *spp; + + REQUIRE(2 == sp.use_count()); + + REQUIRE(! user::isExclusive(uAny)); + + CHECK(nullptr == user::ExplicitDestructor::last); + const auto oldAddress = uAny.state(); + REQUIRE(oldAddress == &*sp); + sp.reset(); + REQUIRE(user::isExclusive(uAny)); + + uAny = 5; + REQUIRE(oldAddress == user::ExplicitDestructor::last); + } + + SECTION("roundtrip io affordance") { + using IOType = zoo::AnyContainer>; + IOType io = 8; + REQUIRE("8" == io.stringize()); + io.fromString("42"); + REQUIRE(42 == *io.state()); + } + + SECTION("copy on write") { + using CopyOnWritable = zoo::AnyContainer>; + + CopyOnWritable a = std::string{"foo"}; + auto aState = a.state(); + REQUIRE("foo" == *aState); + + REQUIRE(user::isExclusive(a)); + + auto b = a; + REQUIRE(!user::isExclusive(a)); + // proves that a and b share the same object + REQUIRE(aState == b.state()); + // now we're going to write to b! then the copy must happen. + b.fromString("bar"); + + REQUIRE(user::isExclusive(a)); + REQUIRE(user::isExclusive(b)); + + auto bState = b.state(); + REQUIRE(aState != bState); + REQUIRE("foo" == *aState); + REQUIRE("bar" == *bState); + } } diff --git a/test/inc/demo/type_erasure_shared_pointer_value_manager.hpp b/test/inc/demo/type_erasure_shared_pointer_value_manager.hpp index d7b0760..9f32f20 100644 --- a/test/inc/demo/type_erasure_shared_pointer_value_manager.hpp +++ b/test/inc/demo/type_erasure_shared_pointer_value_manager.hpp @@ -13,11 +13,17 @@ struct ExplicitDestructor { template struct SharedPointerOptIn: std::false_type {}; + template<> struct SharedPointerOptIn: std::true_type {}; + template<> struct SharedPointerOptIn: std::true_type {}; +template<> +struct SharedPointerOptIn: std::true_type {}; + + template struct UserValueManagement { /// abbreviation @@ -37,9 +43,16 @@ struct UserValueManagement { using VP = std::shared_ptr; /// Abbreviation using SPM = SharedPointerManager; - + struct ExclusiveAware {}; + + SharedPointerManager(): Base(&Operations) { + new(sharedPointer()) VP; + } + + // not part of the end-user interface VP *sharedPointer() noexcept { return this->space_.template as(); } + // part of the end-user interface V *value() noexcept { return &**sharedPointer(); } const V *value() const noexcept { @@ -57,27 +70,41 @@ struct UserValueManagement { } static void copyOp(void *to, const void *from) { - auto downcast = static_cast(from); - new(to) SPM(*downcast); + auto downcast = static_cast(const_cast(from)); + auto destValueManager = new(to) SPM; + *destValueManager->sharedPointer() = *downcast->sharedPointer(); } constexpr static inline typename GP::VTable Operations = { AffordanceSpecifications::template Operation... }; + auto isExclusive() const noexcept { + auto sp = const_cast(this)->sharedPointer(); + return 1 == sp->use_count(); + } + + auto makeExclusive() { + *sharedPointer() = std::make_shared(*value()); + } + + // not user interface SharedPointerManager(SharedPointerManager &&donor) noexcept: Base(&Operations) { new(sharedPointer()) VP(std::move(*donor.sharedPointer())); } - SharedPointerManager(const SharedPointerManager &donor) noexcept: - Base(&Operations) - { - new(sharedPointer()) VP(*const_cast(donor).sharedPointer()); - } + // not user interface +// SharedPointerManager(const SharedPointerManager &model) noexcept: +// Base(&Operations) +// { +// new(sharedPointer()) VP(*const_cast(model).sharedPointer()); +// } + + // internal interface of Builder (important) template SharedPointerManager(Args &&...args): Base(&Operations) @@ -91,6 +118,7 @@ struct UserValueManagement { }; struct AdaptedPolicy: GP::Policy { + // Builders is the old name to refer to what I now call "Value Manager" template using Builder = std::conditional_t<