diff --git a/benchmark/cm/main.cpp b/benchmark/cm/main.cpp index 73aeb053..ae9e7fb9 100644 --- a/benchmark/cm/main.cpp +++ b/benchmark/cm/main.cpp @@ -28,7 +28,7 @@ class square : public shape_base square(f32 SideInit) : Side(SideInit) {} f32 AreaNP() {return Side*Side;} virtual f32 Area() {return Side*Side;} - + private: f32 Side; }; @@ -39,7 +39,7 @@ class rectangle : public shape_base rectangle(f32 WidthInit, f32 HeightInit) : Width(WidthInit), Height(HeightInit) {} f32 AreaNP() {return Width*Height;} virtual f32 Area() {return Width*Height;} - + private: f32 Width, Height; }; @@ -50,7 +50,7 @@ class triangle : public shape_base triangle(f32 BaseInit, f32 HeightInit) : Base(BaseInit), Height(HeightInit) {} f32 AreaNP() {return 0.5f*Base*Height;} virtual f32 Area() {return 0.5f*Base*Height;} - + private: f32 Base, Height; }; @@ -61,7 +61,7 @@ class circle : public shape_base circle(f32 RadiusInit) : Radius(RadiusInit) {} f32 AreaNP() {return Pi32*Radius*Radius;} virtual f32 Area() {return Pi32*Radius*Radius;} - + private: f32 Radius; }; @@ -77,7 +77,7 @@ f32 TotalAreaVTBL(u32 ShapeCount, shape_base **Shapes) { Accum += Shapes[ShapeIndex]->Area(); } - + return Accum; } @@ -209,7 +209,7 @@ enum shape_type : u32 Shape_Rectangle, Shape_Triangle, Shape_Circle, - + Shape_Count, }; @@ -223,17 +223,17 @@ struct shape_union f32 GetAreaSwitch(shape_union Shape) { f32 Result = 0.0f; - + switch(Shape.Type) { case Shape_Square: {Result = Shape.Width*Shape.Width;} break; case Shape_Rectangle: {Result = Shape.Width*Shape.Height;} break; case Shape_Triangle: {Result = 0.5f*Shape.Width*Shape.Height;} break; case Shape_Circle: {Result = Pi32*Shape.Width*Shape.Width;} break; - + case Shape_Count: {} break; } - + return Result; } @@ -244,7 +244,7 @@ f32 GetAreaSwitch(shape_union Shape) f32 TotalAreaSwitch(u32 ShapeCount, shape_union *Shapes) { f32 Accum = 0.0f; - + for(u32 ShapeIndex = 0; ShapeIndex < ShapeCount; ++ShapeIndex) { Accum += GetAreaSwitch(Shapes[ShapeIndex]); @@ -259,7 +259,7 @@ f32 TotalAreaSwitch4(u32 ShapeCount, shape_union *Shapes) f32 Accum1 = 0.0f; f32 Accum2 = 0.0f; f32 Accum3 = 0.0f; - + ShapeCount /= 4; while(ShapeCount--) { @@ -267,10 +267,10 @@ f32 TotalAreaSwitch4(u32 ShapeCount, shape_union *Shapes) Accum1 += GetAreaSwitch(Shapes[1]); Accum2 += GetAreaSwitch(Shapes[2]); Accum3 += GetAreaSwitch(Shapes[3]); - + Shapes += 4; } - + f32 Result = (Accum0 + Accum1 + Accum2 + Accum3); return Result; } diff --git a/design/generic-policy.md b/design/generic-policy.md index deb9b462..030ff9ad 100644 --- a/design/generic-policy.md +++ b/design/generic-policy.md @@ -10,7 +10,7 @@ Description of how to add polymorphic interfaces to `AnyContainer` by assembling The great software engineer Louis Dionne explains very well what normal C++ polymorphism, based on `virtual` overrides, leaves to be desired in his project [Dyno](https://github.com/ldionne/dyno). -Additionally, in this repository there are a few extra pointers with regards to more subtle needs for polymorphism, in particular sub-typing relationships as in the original meaning of [Barbara Liskov's substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle) that should not force their representation to be sub-classing in C++. This is better explained in the foundational work by Kevlin Henney, in the [ACCU September 2000 article "From Mechanism to Method: Substitutability"](https://accu.org/index.php/journals/475) and the [C++ Report magazine article with the original implementation of the `any` component](http://www.two-sdg.demon.co.uk/curbralan/papers/ValuedConversions.pdf), which led to `boost::function` and `std::function` and the "mainstreaming" of type erasure as an alternative for polymorphism, this has been a 20 year journey that keeps going. +Additionally, in this repository there are a few extra pointers with regards to more subtle needs for polymorphism, in particular sub-typing relationships as in the original meaning of [Barbara Liskov's substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle) that should not force their representation to be sub-classing in C++. This is better explained in the foundational work by Kevlin Henney, in the [ACCU September 2000 article "From Mechanism to Method: Substitutability"](https://accu.org/index.php/journals/475) and the [C++ Report magazine article with the original implementation of the `any` component](http://www.two-sdg.demon.co.uk/curbralan/papers/ValuedConversions.pdf), which led to `boost::function` and `std::function` and the "mainstreaming" of type erasure as an alternative for polymorphism, this has been a 20 year journey that keeps going. One particularly complete way of doing polymorphism (implementing sub-typing relationships) without inheritance and `virtual` overrides of member functions is "type erasure". Within "type erasure", educator Arthur O'Dwyer has coined the concept of "affordance" to refer to the polymorphic operations a "type erased" object is capable of doing, described in his excellent blog article ["What is Type Erasure?"](https://quuxplusone.github.io/blog/2019/03/18/what-is-type-erasure/). @@ -96,7 +96,7 @@ In this example, the container implementations and the affordance of copyability template struct GenericPolicy { struct VTable: Affordances::VTableEntry... {}; - using VTHolder = VTableHolder; + using VTHolder = VTablePointerWrapper; using HoldingModel = void *; struct Container: VTHolder, Affordances::template Mixin... { diff --git a/inc/zoo/Any/VTable.h b/inc/zoo/Any/VTable.h index 32668007..28039281 100644 --- a/inc/zoo/Any/VTable.h +++ b/inc/zoo/Any/VTable.h @@ -1,8 +1,7 @@ #ifndef Zoo_Any_VTable_h #define Zoo_Any_VTable_h -#include "zoo/Any/Traits.h" - +#include "zoo/AlignedStorage.h" #include #include #include @@ -16,7 +15,7 @@ struct TypeErasureOperations { template struct TypeErasedContainer { - alignas(Alignment) char space_[Size]; + AlignedStorage space_; const TypeErasureOperations *vTable_ = &Empty; template @@ -28,7 +27,12 @@ struct TypeErasedContainer { } // note: the policy operations do not assume an "Empty" to allow inheritance - // contravariance of the AnyContainer + // contravariance of a type erasure container. + // What this means is that we might want to override, for example, + // the destructor in a derived class, to replicate the behaviour of virtual + // inheritance or the normal or contravariant of destructors in conditions + // of inheritance. Therefore we can not asssume that we're working directly + // with an `Empty`. inline const static TypeErasureOperations Empty = { [](void *) noexcept {}, reinterpret_cast(copyVTable) @@ -73,9 +77,9 @@ struct SmallBufferTypeEraser: template struct ReferentialTypeEraser: TypeErasedContainer { using Me = ReferentialTypeEraser; - + V *&value() noexcept { return *this->template as(); } - + inline const static TypeErasureOperations VTable = { [](void *who) noexcept { delete static_cast(who)->value(); }, [](void *from, void *to) noexcept { @@ -86,7 +90,7 @@ struct ReferentialTypeEraser: TypeErasedContainer { source->vTable = &TypeErasedContainer::Empty; } }; - + template ReferentialTypeEraser(Args &&...args) { this->vTable_ = &VTable; diff --git a/inc/zoo/Any/VTablePolicy.h b/inc/zoo/Any/VTablePolicy.h index 84b17fcb..81002075 100644 --- a/inc/zoo/Any/VTablePolicy.h +++ b/inc/zoo/Any/VTablePolicy.h @@ -1,8 +1,9 @@ #ifndef ZOO_VTABLE_POLICY_H #define ZOO_VTABLE_POLICY_H -#include "zoo/Any/Traits.h" +#include "zoo/tea/Traits.h" +#include "zoo/tea/VTablePointerWrapper.h" #include "zoo/AlignedStorage.h" #include "zoo/pp/platform.h" @@ -210,24 +211,6 @@ struct CallableViaVTable { }; }; -template -struct VTableHolder { - const VirtualTable *pointer_; - - /// \brief from the vtable returns the entry corresponding to the affordance - template - const typename Affordance::VTableEntry *vTable() const noexcept { - return //static_cast(pointer_); - pointer_->template upcast(); - } - - VTableHolder(const VirtualTable *p): pointer_(p) {} - - auto pointer() const noexcept { return pointer_; } - - void change(const VirtualTable *p) noexcept { pointer_ = p; } -}; - template struct BuilderDecider { constexpr static auto NoThrowMovable = @@ -251,7 +234,7 @@ struct GenericPolicy { } }; - using VTHolder = VTableHolder; + using VTHolder = VTablePointerWrapper; struct MSVC_EMPTY_BASES Container: VTHolder, diff --git a/inc/zoo/AnyContainer.h b/inc/zoo/AnyContainer.h index c0bdf7b1..7563bec9 100644 --- a/inc/zoo/AnyContainer.h +++ b/inc/zoo/AnyContainer.h @@ -2,7 +2,8 @@ #define ZOO_ANYCONTAINER_H #include "zoo/pp/platform.h" -#include "zoo/Any/Traits.h" +#include "zoo/tea/Traits.h" +#include "zoo/tea/AffordsCopying.hpp" #include "zoo/utility.h" #include "zoo/meta/NotBasedOn.h" @@ -24,32 +25,7 @@ struct PolicyDefaultBuilder> { using type = typename P::DefaultImplementation; }; -template -struct MemoryLayoutHasCopy: std::false_type {}; -template -struct MemoryLayoutHasCopy< - Policy, - std::void_t ->: std::true_type {}; - -template -struct ExtraAffordanceOfCopying: std::false_type {}; -template -struct ExtraAffordanceOfCopying< - Policy, - std::void_t ->: std::true_type {}; - -/// Copy constructibility and assignability are fundamental operations that -/// can not be enabled/disabled with SFINAE, this trait is to detect copyability -/// in a policy -template -using AffordsCopying = - std::disjunction< - MemoryLayoutHasCopy, ExtraAffordanceOfCopying - >; - -} +} // detail template struct CompositionChain { @@ -90,7 +66,7 @@ struct MSVC_EMPTY_BASES AnyContainerBase: using Policy = Policy_; using SuperContainer = typename CompositionChain::Base; using Container = typename Policy::MemoryLayout; - constexpr static auto Copyable = detail::AffordsCopying::value; + constexpr static auto Copyable = tea::detail::AffordsCopying::value; AnyContainerBase() noexcept: SuperContainer(SuperContainer::Token, nullptr) @@ -316,10 +292,10 @@ struct AnyCopyable: AnyContainerBase { Base(Base::Token, model) { auto source = model.container(); - if constexpr(detail::MemoryLayoutHasCopy::value) { + if constexpr(tea::detail::MemoryLayoutHasCopy::value) { source->copy(this->container()); } else { - static_assert(detail::ExtraAffordanceOfCopying::value); + static_assert(tea::detail::ExtraAffordanceOfCopying::value); Policy::ExtraAffordances::copy(this->container(), source); } } @@ -334,10 +310,10 @@ struct AnyCopyable: AnyContainerBase { myself->destroy(); try { auto source = model.container(); - if constexpr(detail::MemoryLayoutHasCopy::value) { + if constexpr(tea::detail::MemoryLayoutHasCopy::value) { source->copy(this->container()); } else { - static_assert(detail::ExtraAffordanceOfCopying::value); + static_assert(tea::detail::ExtraAffordanceOfCopying::value); Policy::ExtraAffordances::copy(this->container(), source); } } catch(...) { @@ -354,7 +330,7 @@ struct AnyCopyable: AnyContainerBase { template #define PP_BASE_TYPE \ std::conditional_t< \ - detail::AffordsCopying::value, \ + tea::detail::AffordsCopying::value, \ AnyCopyable, \ AnyContainerBase \ > @@ -378,6 +354,7 @@ T *anyContainerCast(const AnyContainer *ptr) noexcept { return const_cast(ptr->template state()); } + } #endif diff --git a/inc/zoo/tea/AffordsCopying.h b/inc/zoo/tea/AffordsCopying.h new file mode 100644 index 00000000..e59b229d --- /dev/null +++ b/inc/zoo/tea/AffordsCopying.h @@ -0,0 +1,10 @@ + AnyContainerBase(AnyContainerBase &&moveable) noexcept: + SuperContainer( + SuperContainer::Token, + static_cast(moveable) + ) + { + auto source = moveable.container(); + source->move(container()); + } + diff --git a/inc/zoo/tea/AffordsCopying.hpp b/inc/zoo/tea/AffordsCopying.hpp new file mode 100644 index 00000000..ebe4e0ec --- /dev/null +++ b/inc/zoo/tea/AffordsCopying.hpp @@ -0,0 +1,38 @@ +#ifndef ZOO_TEA_AFFORDSCOPYING_H +#define ZOO_TEA_AFFORDSCOPYING_H +#include + +namespace zoo::tea { + +namespace detail { + +template +struct MemoryLayoutHasCopy: std::false_type {}; +template +struct MemoryLayoutHasCopy< + Policy, + std::void_t +>: std::true_type {}; + +template +struct ExtraAffordanceOfCopying: std::false_type {}; +template +struct ExtraAffordanceOfCopying< + Policy, + std::void_t +>: std::true_type {}; + +/// Copy constructibility and assignability are fundamental operations that +/// can not be enabled/disabled with SFINAE, this trait is to detect copyability +/// in a policy +template +using AffordsCopying = + std::disjunction< + detail::MemoryLayoutHasCopy, detail::ExtraAffordanceOfCopying + >; + +} // detail + +} // zoo::tea + +#endif diff --git a/inc/zoo/tea/TEC.hpp b/inc/zoo/tea/TEC.hpp new file mode 100644 index 00000000..5893ffed --- /dev/null +++ b/inc/zoo/tea/TEC.hpp @@ -0,0 +1,91 @@ + +#include "zoo/AlignedStorage.h" +#include "zoo/pp/platform.h" +#include "zoo/tea/Traits.h" +#include "zoo/tea/VTablePointerWrapper.h" +#include "zoo/utility.h" + +#include "zoo/meta/copy_and_move_abilities.h" +#include + +namespace zoo::tea { + +namespace detail { + +template +struct HasVTableMember: std::false_type {}; + +template +struct HasVTableMember>: std::true_type {}; + +struct Boo { + using VTable = int; +}; + +static_assert(!HasVTableMember::value); +static_assert(HasVTableMember::value); + +} // detail + + +template +struct TEC { + using Policy = Policy_; + static_assert(zoo::tea::detail::HasVTableMember::value); + + TEC() = delete; + TEC(const TEC &) = delete; + + using DefaultManager = typename Policy::MemoryLayout; + + AlignedStorageFor space_; + + using TokenType = void (TEC::*)(); + + constexpr static inline TokenType Token = nullptr; + + template + TEC(TokenType, Params &&...params) + noexcept( + std::is_nothrow_constructible_v + ) + { + space_.template build(std::forward(params)...); + } + +// AnyContainerBase(AnyContainerBase &&moveable) noexcept: +// SuperContainer( +// SuperContainer::Token, +// static_cast(moveable) +// ) +// { +// auto source = moveable.container(); +// source->move(container()); +// } + + + // void move( + + + ~TEC() { + space_.template as()->destroy(); + } + +}; + + +template +struct CopyableTEC: TEC { + +}; + + +struct FoolishPolicy { + using VTable = int; + struct MemoryLayout {}; +}; + + +static_assert(!std::is_move_constructible_v>); + +} diff --git a/inc/zoo/Any/Traits.h b/inc/zoo/tea/Traits.h similarity index 95% rename from inc/zoo/Any/Traits.h rename to inc/zoo/tea/Traits.h index 60589411..31c2ddc1 100644 --- a/inc/zoo/Any/Traits.h +++ b/inc/zoo/tea/Traits.h @@ -1,5 +1,5 @@ -#ifndef ZOO_ANY_Traits_H -#define ZOO_ANY_Traits_H +#ifndef ZOO_TEA_Traits_H +#define ZOO_TEA_Traits_H #include diff --git a/inc/zoo/tea/VTablePointerWrapper.h b/inc/zoo/tea/VTablePointerWrapper.h new file mode 100644 index 00000000..2ee1a966 --- /dev/null +++ b/inc/zoo/tea/VTablePointerWrapper.h @@ -0,0 +1,28 @@ +#ifndef ZOO_VTABLE_POINTER_WRAPPER_H +#define ZOO_VTABLE_POINTER_WRAPPER_H + + +namespace zoo { + +template +struct VTablePointerWrapper { + const VirtualTable *pointer_; + + /// \brief from the vtable returns the entry corresponding to the affordance + template + const typename Affordance::VTableEntry *vTable() const noexcept { + return //static_cast(pointer_); + pointer_->template upcast(); + } + + VTablePointerWrapper(const VirtualTable *p): pointer_(p) {} + + auto pointer() const noexcept { return pointer_; } + + void change(const VirtualTable *p) noexcept { pointer_ = p; } +}; + +} // zoo + +#endif // ZOO_VTABLE_POINTER_WRAPPER_H + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ef981efc..39880626 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -110,7 +110,7 @@ else() set( TYPE_ERASURE_SOURCES any.cpp AlignedStorage.cpp AnyCallable.cpp AnyCallSignature.cpp - AnyExtended.cpp GenericPolicy.cpp FunctionPolicy.cpp + AnyExtended.cpp GenericPolicy.cpp FunctionPolicy.cpp tec/tec.cpp ) set( SWAR_SOURCES diff --git a/test/FunctionPolicy.cpp b/test/FunctionPolicy.cpp index 74fbd67c..4cd4f663 100644 --- a/test/FunctionPolicy.cpp +++ b/test/FunctionPolicy.cpp @@ -109,7 +109,7 @@ TEST_CASE("New zoo function", "[any][generic-policy][type-erasure][functional]") static_assert(is_base_of_v); static_assert(is_same_v); static_assert(is_constructible_v); - static_assert(zoo::detail::AffordsCopying::value); + static_assert(zoo::tea::detail::AffordsCopying::value); RCF withRTTI(std::move(doubler)); REQUIRE(2.0 == withRTTI(1)); auto &type = withRTTI.type2(); diff --git a/test/GenericPolicy.cpp b/test/GenericPolicy.cpp index 781b7fbe..bc6a344d 100644 --- a/test/GenericPolicy.cpp +++ b/test/GenericPolicy.cpp @@ -55,7 +55,7 @@ static_assert(alignof(SizedContainer<7>) == alignof(void *)); // containers without copy are move-only using MoveOnlyPolicy = Policy; -static_assert(!detail::AffordsCopying::value); +static_assert(!tea::detail::AffordsCopying::value); using MOAC = AnyContainer; static_assert(2 * sizeof(void *) == sizeof(MOAC)); static_assert(!is_copy_constructible_v); @@ -63,13 +63,13 @@ static_assert(is_nothrow_move_constructible_v); // containers with copy are copyable using CopyableNonComposedPolicy = Policy; -static_assert(detail::AffordsCopying::value); +static_assert(tea::detail::AffordsCopying::value); using CopyableNonComposedAC = AnyContainer; static_assert(is_copy_constructible_v); static_assert(is_nothrow_move_constructible_v); using CopyableAnyContainerBasedPolicy = DerivedVTablePolicy; -static_assert(detail::AffordsCopying::value); +static_assert(tea::detail::AffordsCopying::value); using DerivedCopyableAC = AnyContainer; static_assert(is_copy_constructible_v); static_assert(is_nothrow_move_constructible_v); 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 d7b0760a..05b0109b 100644 --- a/test/inc/demo/type_erasure_shared_pointer_value_manager.hpp +++ b/test/inc/demo/type_erasure_shared_pointer_value_manager.hpp @@ -37,7 +37,7 @@ struct UserValueManagement { using VP = std::shared_ptr; /// Abbreviation using SPM = SharedPointerManager; - + VP *sharedPointer() noexcept { return this->space_.template as(); } V *value() noexcept { return &**sharedPointer(); } diff --git a/test/tec/tec.cpp b/test/tec/tec.cpp new file mode 100644 index 00000000..17e79a36 --- /dev/null +++ b/test/tec/tec.cpp @@ -0,0 +1,5 @@ +#include "zoo/tea/TEC.hpp" + +namespace zoo::tea { + +};