From ab7b71433ef8ba4d5cffbc1f292813014a9770c1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 10 Dec 2025 20:17:21 -0500 Subject: [PATCH] xo-facet: header impl + docs --- docs/CMakeLists.txt | 2 +- docs/glossary.rst | 3 + docs/implementation.rst | 93 +++++ docs/index.rst | 1 + include/xo/facet/OObject.hpp | 193 ++++++++++ include/xo/facet/OUniqueBox.hpp | 74 ++++ include/xo/facet/RRouter.hpp | 51 +++ include/xo/facet/{AFacet.hpp => facet.hpp} | 17 +- include/xo/facet/facet_implementation.hpp | 122 +++++++ include/xo/facet/facet_rtti.hpp | 30 ++ include/xo/facet/obj.hpp | 101 +++++ include/xo/facet/typeseq.hpp | 53 +++ utest/objectmodel.test.cpp | 406 +++++++++++++++++++-- 13 files changed, 1112 insertions(+), 34 deletions(-) create mode 100644 docs/implementation.rst create mode 100644 include/xo/facet/OObject.hpp create mode 100644 include/xo/facet/OUniqueBox.hpp create mode 100644 include/xo/facet/RRouter.hpp rename include/xo/facet/{AFacet.hpp => facet.hpp} (75%) create mode 100644 include/xo/facet/facet_implementation.hpp create mode 100644 include/xo/facet/facet_rtti.hpp create mode 100644 include/xo/facet/obj.hpp create mode 100644 include/xo/facet/typeseq.hpp diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 0e07222..4a05291 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -7,7 +7,7 @@ xo_docdir_sphinx_config( glossary.rst #install.rst #introduction.rst - #implementation.rst + implementation.rst ) # see xo-reader/doc or xo-unit/doc for working examples diff --git a/docs/glossary.rst b/docs/glossary.rst index 73dd5ea..9703e5e 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -6,3 +6,6 @@ Glossary .. glossary:: fomo | facet object model + + xfer + | abbreviation for transfer diff --git a/docs/implementation.rst b/docs/implementation.rst new file mode 100644 index 0000000..92891c9 --- /dev/null +++ b/docs/implementation.rst @@ -0,0 +1,93 @@ +.. _implementation: + +Components +========== + +Library dependency tower for *xo-facet*: + +.. ditaa:: + + +-----------------+ + | xo_facet | + +-----------------+ + | xo_cmake | + +-----------------+ + +Abstraction tower for *xo-facet* components. + +.. ditaa:: + :--scale: 0.85 + + +--------------------------------+ + | obj(A,D) | + +--------------------------------+ + | RRouter(A,D) | + +--------------------------------+ + | OObject(A,D) | + +--------------------------------+ + | FacetImplmentationType(A,D) | + +----------------+---------------+ + | facet [A] | data [D] | + +----------------+---------------+ + + +Decorated with sample method calls, to reveal type recovery + +.. ditaa:: + :--scale: 0.85 + + +--------------------------------+ + | obj(A,D) | x.foo() + +--------------------------------+ + | RRouter(A,D) | x.foo() + +--------------------------------+ + | OObject(A,D) | x.iface_.foo(x.data_) + +--------------------------------+ + | FacetImplmentationType(A,D) | x.foo(void*data) + +----------------+---------------+ + | facet A | data D | virtual x.foo(void* data) + +----------------+---------------+ + +.. list-table:: Descriptions + :header-rows: 1 + :widths: 30 30 60 + + * - Component + - Use + - Description + * - obj + - x.foo() + - convenience wrapper with interface A, with state D* + * - RRouter + - x.foo() + - auto injects data pointer + * - OObject + - x.iface()->foo(x.data(), ..) + - fat object pointer. combine i/face + data pointer. + * - FacetImplementationType + - x.foo(void* data, ..) + - implement facet for a particular state datatype; + explicit type-erased state + * - facet + - x.foo(void* data, ..)=0 + - fully abstract interface; explicit type-erased state + +.. uml:: + :caption: fat-object-pointer layout + :scale: 99% + :align: center + + object z1<> + z1 : iface = vt1 + z1 : data = d1 + + object vt1<> + vt1 : foo() + vt1 : bar() + + object d1<> + d1 : x = 0.6 + d1 : y = 0.8 + + z1 o-- vt1 + z1 o-- d1 diff --git a/docs/index.rst b/docs/index.rst index bc230b0..68bba28 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -49,6 +49,7 @@ This gives us several benefits: :maxdepth: 2 :caption: xo-facet contents + implementation glossary genindex search diff --git a/include/xo/facet/OObject.hpp b/include/xo/facet/OObject.hpp new file mode 100644 index 0000000..7c87a66 --- /dev/null +++ b/include/xo/facet/OObject.hpp @@ -0,0 +1,193 @@ +/** @file OObject.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "facet_implementation.hpp" +#include "typeseq.hpp" +#include +#include + +namespace xo { + namespace facet { + template + consteval bool valid_object_traits() + { + static_assert(requires { typename OObject::AbstractInterface; }, + "OObject type must provide typename Object::AbstractInterface"); + static_assert(requires { typename OObject::ISpecific; }, + "OObject type must provide typename Object::ISpecific"); + static_assert(requires { typename OObject::DataType; }, + "OObject type must provide typename Object::DataType"); + static_assert(valid_facet_implementation, + "OObject::ISpecific must implement Object::AbstractInterface"); + static_assert(std::is_standard_layout_v, + "OObject must have standard layout, i.e. no virtual methods." + " Virtual methods belong in OObject::AbstractInterface"); + static_assert(requires(const OObject & obj) { + { obj.iface() } -> std::convertible_to; }, + "OObject must have non-virtual method iface()" + " returning const OObject::AbstractInterface"); + static_assert(requires(const OObject & obj) { + { obj.data() } -> std::convertible_to; }, + "OObject must have non-virtual method data() returning OObject::DataType*"); + + return true; + } + + /** A "fat object pointer": combines two pointers: + * + * 1. behavior: an interface pointer + * (implementation of @tparam AFacet a.k.a. vtable pointer) + * for passive state DRepr. + * Interface pointers are static globals. + * + * 2. state: a data pointer to instance of passive state DRepr + * An OObject instance does not own its data pointer + * + * Performance note: when DRepr can be determined at compile time, + * it's often feasible to optimize away the interface part. + * + * Runtime polymorphism when @tparam DRepr is @ref DVariantPlaceholder + * + * Application code should not use this directly. + * Instead, inherit a facet-specific routing wrapper that automatically + * injects @ref data as first argument to @ref iface_ methods. + **/ + template + struct OObject { + using FacetType = AFacet; + using ISpecific = FacetImplType; + using DataType = DRepr; + using DataPtr = DRepr*; + + explicit OObject() {} + explicit OObject(DataPtr d) : data_{d} {} + + /** trivial: nothing to do for @ref iface_ and does not own @ref data_ **/ + ~OObject() = default; + + /** OObject is truthy **/ + operator bool() const { return data_ != nullptr; } + + /** interface pointer for variant OObject instances. + * These instance support runtime polymorphism. + **/ + const FacetType * iface() const + requires std::is_same_v + { + /* std::launder: + * + * contents of iface_ at runtime will not match + * compile-time datatype. This prohibits compiler de-virtualizing + * calls to ISpecific methods, based on mistaken belief that + * vtable pointer is known at compile time. + */ + return std::launder(&iface_); + } + + /** interface pointer for OObject instance with representation + * known at compile time. + * + * Calls here should be straightforward to devirtualize + **/ + const FacetType * iface() const + requires (!std::is_same_v) + { + /* don't use std::launder: want compiler to devirtualize + * calls to virtual @ref iface_ methods + */ + return &iface_; + } + + DataPtr data() const { return data_; } + + void reset() { data_ = nullptr; } + + /** + * We're either: + * - assigning from pointer with compatible representation + * - implementing the fat-object-pointer equivalent of + * assigning a derived pointer to a base pointer. + **/ + template + OObject & from_data(DOther * other) { + static_assert(std::is_same_v + || std::is_convertible_v); + + if constexpr (std::is_convertible_v) { + /** assigning from data with same representation **/ + this->data_ = other; + } else /*DRepr is DVariantPlaceholder*/ { + /** assigning to variant **/ + + /* acquire fat object pointer for (AFacet, DOther) */ + OObject oother(other); + + static_assert(sizeof(*this) == sizeof(oother)); + + ::memcpy((void*)this, (void*)&oother, sizeof(*this)); + } + + return *this; + } + + /** + * Downcast to pointer of type DOther*, if valid. + * Provided when actual type of @ref data_ is not DRepr, + * because DRepr is DVariantPlaceholder. + * + * We can't rely on dynamic_cast here, because DRepr's + * don't need to be related as far as c++ type system is + * concerned. + **/ + template + DOther * downcast() + requires (std::is_same_v) + { + if (data_ && (typeseq::id() == this->iface()->_typeseq())) { + /* actual runtime type for data_ is DOther, + * safe to reinterpret + */ + return reinterpret_cast(data_); + } else { + return nullptr; + } + } + + DRepr & operator*() { return *data_; } + +#ifdef NOT_IN_USE + // not sure if this is a good idea. could just as well write + // auto obj = ...; + // *obj.data() == rhs + + /** assign contents of rhs in-place **/ + OObject & operator=(const DRect & rhs) { + assert(data_); + + *(this->data_) = rhs; + + return *this; + } +#endif + + /** fetch data pointer. load-bearing for routing classes **/ + static bool _valid; + + /** runtime interface for this object **/ + ISpecific iface_; + /** runtime state for this object **/ + DataPtr data_ = nullptr; + }; + + template + bool + OObject::_valid = valid_object_traits(); + + } /*namespace facet*/ +} /*namespace xo*/ + +/* end OObject.hpp */ diff --git a/include/xo/facet/OUniqueBox.hpp b/include/xo/facet/OUniqueBox.hpp new file mode 100644 index 0000000..d22ad70 --- /dev/null +++ b/include/xo/facet/OUniqueBox.hpp @@ -0,0 +1,74 @@ +/** @file OUniqueBox.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +namespace xo { + namespace facet { + /** + * Uniquely-owned instance with runtime polymorphism. + * + * Reminder that in the facet object model we expect + * objects to be transient. + * + + * + * Unlike OUniqueBox can use for variant data + * without additional overhead. Tradeoff is that avoiding such + * overhead excludes std::unique_ptr. + * + * We're going to instead rely on AInterface providing a destruct_data() method, + * so in practice get the deleter from interface state. + * + * Possibly means we need all abstract interfaces to share a common base + * + * Remarks: + * - when @tparam Data is supplied + **/ + template + struct OUniqueBox { + using AbstractInterface = AInterface; + using ISpecific = ISpecificFor::ImplType; + /* note: Data can be void here */ + using DataType = Data; + using DataBox = Data*; + + explicit OUniqueBox() {} + /* unsatisfactory b/c doesn't enforce that @p d is heap-allocated */ + explicit OUniqueBox(DataBox d) : data_{std::move(d)} {} + + ~OUniqueBox() { + if (data_ != nullptr) { + this->iface()->destruct_data(data_); + delete data_; + this->data_ = nullptr; + } + } + + const AInterface * iface() const + requires std::is_same_v + { + return std::launder(&iface_); + } + + const AInterface * iface() const + requires (!std::is_same_v) + { + return &iface_; + } + + /** note: would prefer this to be constexpr, but not simple asof gcc 14.3 **/ + static bool _valid; + + /** note: load-bearing for routing classes such as RComplex **/ + Data * data() const { return data_; } + + ISpecific iface_; + DataBox data_ = nullptr; + }; + + + } +} /*namespace xo*/ + +/* end OUniqueBox.hpp */ diff --git a/include/xo/facet/RRouter.hpp b/include/xo/facet/RRouter.hpp new file mode 100644 index 0000000..daa7887 --- /dev/null +++ b/include/xo/facet/RRouter.hpp @@ -0,0 +1,51 @@ +/** @file RRouter.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "OObject.hpp" + +namespace xo { + namespace facet { + template + consteval bool valid_object_router() + { + static_assert(requires { typename RRouter::ObjectType; }, + "Router type must provide typename Router::ObjectType"); + static_assert(valid_object_traits, + "Router::ObjectType must satisfy objectmodel traits"); + static_assert(std::is_standard_layout_v, + "Router must have standard laayout, i.e. no virtual methods." + " Virtual methods belong in OObject::AbstractInterface*>"); + return true; + }; + + /** + * template + * struct RMyFacet : public Object { + * using ObjectType = Object; + * + * RObject() = default; + * RObject(Object::DataPtr data) : Object{data} {} + * + * void something() const { return Object::iface()->something(Object::data()); } + * int andalso(double somearg) const { return Object::iface()->andalso(Object::data(), somearg); } + * }; + * + * template + * struct RoutingFor { + * using RoutingType = RMyFacet; + * }; + **/ + template + requires abstract_facet + struct RoutingFor; + + template + using RoutingType = RoutingFor::RoutingType; + } +} /*namespace xo*/ + +/* end RRouter.hpp */ diff --git a/include/xo/facet/AFacet.hpp b/include/xo/facet/facet.hpp similarity index 75% rename from include/xo/facet/AFacet.hpp rename to include/xo/facet/facet.hpp index 2e48eb6..b4cd1f0 100644 --- a/include/xo/facet/AFacet.hpp +++ b/include/xo/facet/facet.hpp @@ -7,10 +7,16 @@ #include #include +#include namespace xo { namespace facet { + // ----- abstract facet ----- + namespace detail { + /** aux class to support facet validation + * see @ref abstract_facet, @ref valid_abstract_facet + **/ struct PlaceholderAbstractInterface { virtual double foo(void * data) const = 0; }; @@ -35,10 +41,13 @@ namespace xo { /** Use: when defining an abstract facet AMyFacet * * struct AMyFacet { - * virtual void foo(void * data) const = 0; + * virtual void something(void * data) const = 0; + * virtual int andalso(void * data, double somearg) const = 0; + * + * static bool _valid; * }; * - * static_assert(valid_abstract_facet()); + * bool AMyFacet::_valid = valid_abstract_facet(); * **/ template @@ -59,6 +68,10 @@ namespace xo { static_assert (std::is_trivially_destructible_v, "Abstract facet expected to have trivial dtor since no state"); + static_assert + (requires(const T & facet) { + { facet._typeseq() } -> std::convertible_to; }, + "Abstract facet must provide a _typeseq() method for safe downcasting"); return true; }; diff --git a/include/xo/facet/facet_implementation.hpp b/include/xo/facet/facet_implementation.hpp new file mode 100644 index 0000000..8565ab4 --- /dev/null +++ b/include/xo/facet/facet_implementation.hpp @@ -0,0 +1,122 @@ +/** @file facet_implementation.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +//#include "xo/facet/facet.hpp" + +#include +#include + +namespace xo { + namespace facet { + // ----- facet implementation ----- + + // An implementation provides behavior (i.e. code, not data) + // for a particular abstract facet, specialized for a specific data type + + /** For example ISpecific = IComplex_DPolarCoords + **/ + template + concept implements_facet = requires { + std::is_base_of_v; + std::is_default_constructible_v; + std::is_standard_layout_v; + /** require no additional state **/ + sizeof(ISpecific) == sizeof(AFacet); + }; + + /** Use: when defining datatype recovery for a typed facet implementation: + * + * template + * struct AMyFacet_Impl : public AMyFacet { + * static void _something(Repr *); + * static int _andalso(Repr *, double somearg); + * + * virtual void something(void * data) const final override { + * _something((Repr *)data); + * } + * + * virtual int andalso(void * data, double somearg) const final override { + * _andalso((Repr *)data, somearg); + * } + * + * static bool _valid; + * }; + * + * template + * bool AMyFacet_Impl::_valid + * = valid_facet_implementation(); + **/ + template + consteval bool valid_facet_implementation() + requires (valid_abstract_facet()) + { + static_assert(std::is_base_of_v, + "Facet implementation must inherit FacetRttiShim"); + static_assert(std::is_default_constructible_v, + "Facet implementation must be default-constructible"); + static_assert(sizeof(ISpecific) == sizeof(AFacet), + "Facet implementation may not introduce state"); + static_assert(!std::has_virtual_destructor_v, + "Facet implementation does not benefit from virtual dtor (since has no data)"); + static_assert(std::is_trivially_destructible_v, + "Facet implementation expected to have trivial dtor (since has no data)"); + + // don't need this test, it's covered by sizeof check + //static_assert(std::is_pointer_interconvertible_base_of_v, + // "Interface implementation must directly inherit interface (no base offset)"); + + return true; + }; + + /** Compile-time facet implementation lookup + * + * @c FacetImplementation::ImplType + * gives the type that implements @c AMyFacet with state @c DSomeRepr + * + * template + * struct FacetImplementation { + * using ImplType = IMyFacet_Impl; + * }; + * + * template<> + * struct FacetImplementation { + * using ImplType = IMyFacet_Any; + * }; + * + **/ + template + struct FacetImplementation {}; + + /** Retrieve facet implementation for a (facet, datatype) pair **/ + template + using FacetImplType = FacetImplementation::ImplType; + + /** Data type for facet implementation that supports runtime polymorphism. + * Implementation will stub all methods, since they will never be invoked. + * + * template <> + * struct IMyFacet_Any : public AMyFacet { + * virtual void something(void * data) const final override { assert(false); } + * virtual int andalso(void * data) const final override { assert(false); return 0; } + * + * static bool _valid; + * } + * + * template <> + * bool IMyFacet_Any::_valid + * = valid_facet_implementation(); + **/ + struct DVariantPlaceholder {}; + + /** PLAN: + * expect also will need runtime version of FacetImplementation. + **/ + + } /*namespace facet*/ +} /*namespace xo*/ + +/* end facet_implementation.hpp */ diff --git a/include/xo/facet/facet_rtti.hpp b/include/xo/facet/facet_rtti.hpp new file mode 100644 index 0000000..416f7da --- /dev/null +++ b/include/xo/facet/facet_rtti.hpp @@ -0,0 +1,30 @@ +/** @file facet_rtti.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "facet.hpp" +#include + +namespace xo { + namespace facet { + /** CRTP here + * + * to be able to recover original type from a variant, + * we need some analog of dynamic_cast<>. + * We don't use c++ dynamic cast here because we + * are refusing to impose any restrictions on + * facet representation types, so in particular + * they will likely be of unrelated type. + * + * Instead rely on the interface pointer to access + * information about the runtime type associated + * with a stored fat-object-pointer representation. + **/ + template + struct FacetRttiShim : public AFacet { + }; + } /*namespace facet*/ +} /*namespace xo*/ + +/* end facet_rtti.hpp */ diff --git a/include/xo/facet/obj.hpp b/include/xo/facet/obj.hpp new file mode 100644 index 0000000..f23ae79 --- /dev/null +++ b/include/xo/facet/obj.hpp @@ -0,0 +1,101 @@ +/** @file obj.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include "RRouter.hpp" + +namespace xo { + namespace facet { + /** object with borrowed state pointer + * - With default Data argument: + * type-erased polymorphic container + * - with specific Data argument: + * typed container. Trivially de-virtualizable + * + * Example: + * std::unique_ptr z1_in + * = std::make_unique(1.0, 0.0): + * ubox z1{z1_in.release()}; + * z1.xcoord(); + * + * + * +-----+ +-----------------+ + * Interface | x-------------->| vtable for | + * +-----+ | some descendant | + * Data | x--------\ | of AInterface | + * +-----+ | | | + * | +-----------------+ + * | + * | +--------------+ + * \----->| data :: Repr | + * +--------------+ + * + * Binary representation of unay + * is compatible for different values of @tparam Data + * as long as vtable pointer moves along with data pointer. + * + * In particular binary representation for + * ubox is as if it inherited ubox + * (even though it does not as far as compiler is concerned) + * + * This is load-bearing for @ref move2any see below + **/ + template + struct obj : public RoutingType> { + using Super = RoutingType>; + + obj() {} + explicit obj(Super::DataPtr d) : Super(d) {} + + /** copy constructor **/ + template + obj(const obj && other) : Super() + { + if constexpr (std::is_convertible_v) { + this->data_ = other.data_; + } else if constexpr (std::is_same_v) { + this->from_data(other.data_); + } else { + /* still need something for downcasting */ + + static_assert(false, "expect DOther compatible with DRepr"); + } + } + + /** move constructor from a different representation. + * allowed given: + * - same abstract interface + * - same strategy for holding state (naked / unique / refcounted ...) + **/ + template + obj(const obj && other) + requires (std::is_same_v + || std::is_convertible_v) + : Super() + { + static_assert(sizeof(obj) + == sizeof(obj)); + + other.move2any(this); + + assert(other.data_ = nullptr); + } + + /** safe downcast from variant. null if downcast fails **/ + static obj from(const OObject & other) { + return obj(other.template downcast()); + } + }; + + template + inline obj + with_facet(DRepr * data) { + return obj(data); + } + } /*namespace facet*/ +} /*namespace xo*/ + +/* end obj.hpp */ diff --git a/include/xo/facet/typeseq.hpp b/include/xo/facet/typeseq.hpp new file mode 100644 index 0000000..d54ac98 --- /dev/null +++ b/include/xo/facet/typeseq.hpp @@ -0,0 +1,53 @@ +/** @file typeseq.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include + +namespace xo { + namespace facet { + /** + * Tag here so we can preserve header-only implementation + * and still have static variable + */ + template + struct typeseq_impl { + /** Can't have this be constexpr. + * We need ids in shared libraries to be generated + * at load time to avoid false positives + * + * Return unique id number for each type. + * Numbers are sequentially allocated, so can use + * as vector indices + * + * Conversely note that built-in typeinfo may + * return false negatives across library boundaries + * when using clang. + **/ + template + static int32_t id() { + static bool armed = true; + static int32_t id = 0; + + if (armed) { + armed = false; + id = ++s_next_id; + } + + return id; + } + + static int32_t s_next_id; + }; + + template + int32_t typeseq_impl::s_next_id = 0; + + using typeseq = typeseq_impl<>; + } +} /*namespace xo*/ + +/* end typeseq.hpp */ diff --git a/utest/objectmodel.test.cpp b/utest/objectmodel.test.cpp index 1dd0ece..2156e61 100644 --- a/utest/objectmodel.test.cpp +++ b/utest/objectmodel.test.cpp @@ -3,58 +3,402 @@ * @author Roland Conybeare, Dec 2025 **/ -#include "xo/facet/AFacet.hpp" +#include "xo/facet/facet.hpp" +#include "xo/facet/facet_implementation.hpp" +#include "xo/facet/OObject.hpp" +#include "xo/facet/RRouter.hpp" +#include "xo/facet/typeseq.hpp" +#include "xo/facet/obj.hpp" #include #include +#include #include #include namespace xo { using xo::facet::valid_abstract_facet; + using xo::facet::valid_facet_implementation; + using xo::facet::FacetImplementation; + using xo::facet::DVariantPlaceholder; + using xo::facet::OObject; + using xo::facet::valid_object_router; + using xo::facet::RoutingType; + using xo::facet::typeseq; + using xo::facet::obj; + using xo::facet::with_facet; + + // ------ AComplex ----- + + /** abstract interface for a complex number **/ + struct AComplex { + using TypeErasedIface = struct IComplex_Any; + + /** RTTI: reports unique id# for actual runtime data representation **/ + virtual int32_t _typeseq() const = 0; + + virtual double xcoord(void * data) const = 0; + virtual double ycoord(void * data) const = 0; + virtual double argument(void * data) const = 0; + virtual double magnitude(void * data) const = 0; + + virtual void destruct_data(void * data) const = 0; + + static bool _valid; + }; + + bool + AComplex::_valid = valid_abstract_facet(); + + // ----- IComplex_Impl ----- + + template + struct IComplex_Impl; + + template + struct IComplex_Xfer : public AComplex { + // parallel interface to AComplex, but with specific data type + using Impl = IComplex_Impl; + + // from FacetRttiShim + + virtual int32_t _typeseq() const { return s_typeseq; } + + // from AComplex + + virtual double xcoord(void * data) const final override { return Impl::xcoord(*(DRepr*)data); } + virtual double ycoord(void * data) const final override { return Impl::ycoord(*(DRepr*)data); } + virtual double argument(void * data) const final override { return Impl::argument(*(DRepr*)data); } + virtual double magnitude(void * data) const final override { return Impl::magnitude(*(DRepr*)data); } + + virtual void destruct_data(void * data) const final override { Impl::destruct_data(*(DRepr*)data); } + + static int32_t s_typeseq; + static bool _valid; + }; + + template + int32_t + IComplex_Xfer::s_typeseq = typeseq::id(); + + template + bool + IComplex_Xfer::_valid = valid_facet_implementation; + + namespace facet { + template + struct FacetImplementation { + using ImplType = IComplex_Xfer; + }; + } + + // ----- IComplex_Any ----- + + /** type-erased implementation of AComplex, for runtime polymorphism + * Usable by (and only by) overwriting with a typed implementation, + * such as IComplex_RectCoords or IComplex_PolarCoords. + **/ + struct IComplex_Any : public AComplex { + virtual int32_t _typeseq() const { return s_typeseq; } + + virtual double xcoord(void *) const final override { assert(false); return 0.0; } + virtual double ycoord(void *) const final override { assert(false); return 0.0; } + virtual double argument(void *) const final override { assert(false); return 0.0; } + virtual double magnitude(void *) const final override { assert(false); return 0.0; } + + virtual void destruct_data(void *) const final override { assert(false); } + + static int32_t s_typeseq; + static bool _valid; + }; + + int32_t + IComplex_Any::s_typeseq = typeseq::id(); + + bool + IComplex_Any::_valid = valid_facet_implementation(); + + namespace facet { + template <> + struct FacetImplementation { + using ImplType = IComplex_Any; + }; + } + + // ---------------------------------------------------------------- + // AComplex, DPolarCoords + // + // complex number represented using polar coordinates (arg, mag) + // ---------------------------------------------------------------- + + struct DPolarCoords { + DPolarCoords(double arg, double mag) : arg_{arg}, mag_{mag} {} + + double arg_; + double mag_; + }; + + using IComplex_DPolarCoords = IComplex_Xfer; + + template <> + struct IComplex_Impl { + static double xcoord(DPolarCoords & self) { return self.mag_ * std::cos(self.arg_); } + static double ycoord(DPolarCoords & self) { return self.mag_ * std::sin(self.arg_); } + static double argument(DPolarCoords & self) { return self.arg_; } + static double magnitude(DPolarCoords & self) { return self.mag_; } + + static void destruct_data(DPolarCoords & self) { self.~DPolarCoords(); } + }; + + // ---------------------------------------------------------------- + // AComplex, DRectCoords + // + // complex number represented using rectangular coordinates (x, y) + // ---------------------------------------------------------------- + + struct DRectCoords { + DRectCoords(double x, double y) : x_{x}, y_{y} {} + + double x_; + double y_; + }; + + using IComplex_DRectCoords = IComplex_Xfer; + + template <> + struct IComplex_Impl { + static double xcoord(DRectCoords & self) { return self.x_; } + static double ycoord(DRectCoords & self) { return self.y_; } + static double argument(DRectCoords & self) { return std::atan(self.y_ / self.x_); } + static double magnitude(DRectCoords & self) { + double x = self.x_; + double y = self.y_; + + return std::sqrt(x*x + y*y); + } + + static void destruct_data(DRectCoords & self) { self.~DRectCoords(); } + }; + + // ---------------------------------------------------------------- + // RComplex + // + // convenience router: supplies data argument to AComplex methods + // ---------------------------------------------------------------- + + template + struct RComplex : public Object { + using ObjectType = Object; + + RComplex() {} + RComplex(Object::DataPtr data) : Object{std::move(data)} {} + + int32_t _typeseq() const { return Object::iface()->_typeseq(); } + double xcoord() const { return Object::iface()->xcoord(Object::data()); } + double ycoord() const { return Object::iface()->ycoord(Object::data()); } + double argument() const { return Object::iface()->argument(Object::data()); } + double magnitude() const { return Object::iface()->magnitude(Object::data()); } + + /** note: would prefer this to be constexpr, but seems infeasible asof gcc 14.3 **/ + static bool _valid; + }; + + template + bool + RComplex::_valid = valid_object_router(); + + namespace facet { + template + struct RoutingFor { + using RoutingType = RComplex; + }; + } /*namespace facet*/ namespace ut { - // ------ AComplex ----- + // ----- TESTS ----- - /** abstract interface for a complex number **/ - struct AComplex { - using TypeErasedIface = struct IComplex_Any; + TEST_CASE("facet-1", "[facet]") + { + // AComplex passes abstract facet checks + REQUIRE(AComplex::_valid); - virtual double xcoord(void * data) const = 0; - virtual double ycoord(void * data) const = 0; - virtual double argument(void * data) const = 0; - virtual double magnitude(void * data) const = 0; + // IComplex_Any passes facet implementation checks + REQUIRE(IComplex_Any::_valid); + } - virtual void destruct_data(void * data) const = 0; + TEST_CASE("xfer-polar-1", "[facet]") + { + IComplex_Impl impl; + DPolarCoords z1{0.0, 1.0}; - private: - static bool _valid; - }; + REQUIRE(decltype(impl)::xcoord(z1) == 1.0); + REQUIRE(decltype(impl)::ycoord(z1) == 0.0); + REQUIRE(decltype(impl)::argument(z1) == 0.0); + REQUIRE(decltype(impl)::magnitude(z1) == 1.0); + } - bool - AComplex::_valid = valid_abstract_facet(); + TEST_CASE("xfer-rect-1", "[facet]") + { + IComplex_Impl impl; + DRectCoords z1{1.0, 0.0}; - // ----- IComplex_Any ----- + REQUIRE(decltype(impl)::xcoord(z1) == 1.0); + REQUIRE(decltype(impl)::ycoord(z1) == 0.0); + REQUIRE(decltype(impl)::argument(z1) == 0.0); + REQUIRE(decltype(impl)::magnitude(z1) == 1.0); + } - /** type-erased implementation of AComplex, for runtime polymorphism - * Usable by (and only by) overwriting with a typed implementation, - * such as IComplex_RectCoords or IComplex_PolarCoords. - **/ - struct IComplex_Any : public AComplex { - virtual double xcoord(void *) const final override { assert(false); return 0.0; } - virtual double ycoord(void *) const final override { assert(false); return 0.0; } - virtual double argument(void *) const final override { assert(false); return 0.0; } - virtual double magnitude(void *) const final override { assert(false); return 0.0; } + TEST_CASE("oobject-polar-1", "[facet]") + { + using Object = OObject; - virtual void destruct_data(void *) const final override { assert(false); } + DPolarCoords z1{0.0, 1.0}; + Object obj(&z1); - private: - static bool _valid; - }; + REQUIRE(obj.iface()->xcoord(obj.data()) == 1.0); + REQUIRE(obj.iface()->ycoord(obj.data()) == 0.0); + REQUIRE(obj.iface()->argument(obj.data()) == 0.0); + REQUIRE(obj.iface()->magnitude(obj.data()) == 1.0); + } - bool - IComplex_Any::_valid = true; //valid_interface_implementation(); + TEST_CASE("oobject-rect-1", "[facet]") + { + using Object = OObject; + DRectCoords z1{1.0, 0.0}; + Object obj(&z1); + REQUIRE(obj.iface()->xcoord(obj.data()) == 1.0); + REQUIRE(obj.iface()->ycoord(obj.data()) == 0.0); + REQUIRE(obj.iface()->argument(obj.data()) == 0.0); + REQUIRE(obj.iface()->magnitude(obj.data()) == 1.0); + } + + TEST_CASE("rrouter-polar-1", "[facet]") + { + using Router = RoutingType>; + + DPolarCoords z1{0.0, 1.0}; + Router obj(&z1); + + REQUIRE(obj.xcoord() == 1.0); + REQUIRE(obj.ycoord() == 0.0); + REQUIRE(obj.argument() == 0.0); + REQUIRE(obj.magnitude() == 1.0); + + } + + TEST_CASE("rrouter-rect-1", "[facet]") + { + using Router = RoutingType>; + + DRectCoords z1{1.0, 0.0}; + Router obj(&z1); + + REQUIRE(obj.xcoord() == 1.0); + REQUIRE(obj.ycoord() == 0.0); + REQUIRE(obj.argument() == 0.0); + REQUIRE(obj.magnitude() == 1.0); + + } + + TEST_CASE("rrouter-any-1", "[facet]") + { + using Router = RoutingType>; + + // variant! + Router var1; + + REQUIRE(var1.iface() != nullptr); + REQUIRE(var1.data() == nullptr); + + { + DRectCoords z1{1.0, 0.0}; + + var1.from_data(&z1); + + REQUIRE(var1.iface() != nullptr); + REQUIRE((void*)var1.data() == (void*)&z1); + + REQUIRE(var1.xcoord() == z1.x_); + REQUIRE(var1.ycoord() == z1.y_); + + REQUIRE(var1.xcoord() == 1.0); + REQUIRE(var1.ycoord() == 0.0); + REQUIRE(var1.argument() == 0.0); + REQUIRE(var1.magnitude() == 1.0); + + REQUIRE(var1.downcast() == nullptr); + REQUIRE(var1.downcast() == &z1); + } + + { + DPolarCoords z2{0.0, 1.0}; + + var1.from_data(&z2); + + REQUIRE(var1.iface() != nullptr); + REQUIRE((void*)var1.data() == (void*)&z2); + + REQUIRE(var1.argument() == z2.arg_); + REQUIRE(var1.magnitude() == z2.mag_); + + REQUIRE(var1.xcoord() == 1.0); + REQUIRE(var1.ycoord() == 0.0); + REQUIRE(var1.argument() == 0.0); + REQUIRE(var1.magnitude() == 1.0); + + REQUIRE(var1.downcast() == nullptr); + REQUIRE(var1.downcast() == &z2); + } + } + + TEST_CASE("obj-rect-1", "[facet]") + { + DRectCoords z1{1.0, 0.0}; + auto z1o = with_facet(&z1); + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + REQUIRE(z1o._typeseq() == typeseq::id()); + REQUIRE(z1o.xcoord() == 1.0); + REQUIRE(z1o.ycoord() == 0.0); + REQUIRE(z1o.argument() == 0.0); + REQUIRE(z1o.magnitude() == 1.0); + + // downcast isn't part of interface for non-variant DRepr + // REQUIRE(z1o.downcast() == &z1); + + double h = 0.5 * std::sqrt(2.0); + DRectCoords z2{h, h}; + + z1o.from_data(&z2); + + REQUIRE(z1o.data() != &z1); + REQUIRE(z1o.data() == &z2); + + REQUIRE(z1o.xcoord() == h); + REQUIRE(z1o.ycoord() == h); + REQUIRE(z1o.argument() == 0.25 * std::numbers::pi); + REQUIRE(z1o.magnitude() == 1.0); + + *z1o = z1; + + REQUIRE(z1o.data() == &z2); + REQUIRE(z1o.xcoord() == 1.0); + REQUIRE(z1o.ycoord() == 0.0); + REQUIRE(z1o.argument() == 0.0); + REQUIRE(z1o.magnitude() == 1.0); + } + + TEST_CASE("obj-any-1", "[facet]") + { + obj var1; + + REQUIRE(!var1); + REQUIRE(var1.iface() != nullptr); + REQUIRE(var1.data() == nullptr); + } } }