diff --git a/CMakeLists.txt b/CMakeLists.txt index 47fa1dc9..de956198 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,7 @@ set(DOX_EXCLUDE_PATTERNS [=[ # in reverse topological order i.e. dependencies first add_subdirectory(xo-cmake) +add_subdirectory(xo-facet) add_subdirectory(xo-indentlog) add_subdirectory(xo-allocutil) add_subdirectory(xo-refcnt) diff --git a/conf.py b/conf.py index 1cb86062..bc201b8b 100644 --- a/conf.py +++ b/conf.py @@ -17,7 +17,7 @@ author = 'Roland Conybeare' extensions = [ "breathe", "sphinx.ext.mathjax", # inline math "sphinx.ext.autodoc", # generate info from docstrings - "sphinxcontrib.ditaa", # diagrams-through-ascii-art +# "sphinxcontrib.ditaa", # diagrams-through-ascii-art "sphinxcontrib.plantuml", # text -> uml diagrams ] diff --git a/default.nix b/default.nix index 4881b50d..e12ad965 100644 --- a/default.nix +++ b/default.nix @@ -421,6 +421,7 @@ in pkgs = pkgs; xo = { cmake = pkgs.xo-cmake; +# facet = pkgs.xo-facet; indentlog = pkgs.xo-indentlog; refcnt = pkgs.xo-refcnt; subsys = pkgs.xo-subsys; diff --git a/index.rst b/index.rst index c656fb70..3e955649 100644 --- a/index.rst +++ b/index.rst @@ -12,6 +12,7 @@ Some features: kalman filters, stochastic processes, complex event processing, s :caption: XO contents docs/install + xo-facet/docs/index xo-alloc/docs/index xo-indentlog/docs/index xo-flatstring/docs/index diff --git a/xo-alloc2/utest/objectmodel.test.cpp b/xo-alloc2/utest/objectmodel.test.cpp index fe48d785..8aed7ad4 100644 --- a/xo-alloc2/utest/objectmodel.test.cpp +++ b/xo-alloc2/utest/objectmodel.test.cpp @@ -261,7 +261,7 @@ namespace xo { }; bool - IComplex_Any::_valid = valid_interface_implementation; + IComplex_Any::_valid = valid_interface_implementation(); // ---------------------------------------------------------------- diff --git a/xo-facet/docs/_static/README b/xo-facet/docs/_static/README new file mode 100644 index 00000000..7297d046 --- /dev/null +++ b/xo-facet/docs/_static/README @@ -0,0 +1 @@ +add any static {.html, .js, ..} files for sphinx to pickup here diff --git a/xo-facet/docs/_static/favicon.ico b/xo-facet/docs/_static/favicon.ico new file mode 100644 index 00000000..4163dd69 Binary files /dev/null and b/xo-facet/docs/_static/favicon.ico differ diff --git a/xo-facet/docs/_static/img/favicon.ico b/xo-facet/docs/_static/img/favicon.ico new file mode 100644 index 00000000..4163dd69 Binary files /dev/null and b/xo-facet/docs/_static/img/favicon.ico differ diff --git a/xo-facet/docs/index.rst b/xo-facet/docs/index.rst index fdbef68a..bc230b00 100644 --- a/xo-facet/docs/index.rst +++ b/xo-facet/docs/index.rst @@ -4,7 +4,46 @@ xo-facet documentation ====================== xo-facet provides an object model that supports runtime polymorphism with interfaces and data kept separate. -Similar to rust traits, haskell type clases, go interfaces. +Design operates on similar lines to rust traits, haskell type clases, and go interfaces. + +Principles +---------- + +* Keep interfaces and data structures separate. + An object is represented using a combination of exactly two pointers: + an interface pointer and a data pointer. + +* An interface pointer implements an abstract facet. + A facet has only abstract methods, and no state. + +* An interface pointer is analogous to a vtable pointer in a regular + c++ object. It identifies a suite of related functions that operate + on a particular data type. + +* A data pointer is like a pointer to a c struct. + Data objects are passive, except for necessary ctors/dtors. + Runtime polymorphism works seamlessly across different data types + without requiring any prearrangement such as sharing a common + base class. + +* We make 'familiar c++ objects', on demand, by pairing an interface pointer + with a data pointer. Unlike usual c++ practice, we expect such objects + to be transient. To represent persistent state, we rely + solely on data pointers. + +* Since interface+data are separate, + we can easily swap out one interface for another. + +This gives us several benefits: + +* A data type can easily particpate in polymorphism across different facets, + without complicating object representation. To convert an object to use a + different facet, we just swap out the interface pointer. + +* Interface and data pointers can arrive at the doorstep of a computation + by different pathways. Often the pathway for an interface pointer is + simpler that the pathway for a data pointer. This increases scope for + devirtualization. .. toctree:: :maxdepth: 2 diff --git a/xo-facet/include/xo/facet/AFacet.hpp b/xo-facet/include/xo/facet/AFacet.hpp new file mode 100644 index 00000000..2e48eb61 --- /dev/null +++ b/xo-facet/include/xo/facet/AFacet.hpp @@ -0,0 +1,68 @@ +/** @file AFacet.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include +#include + +namespace xo { + namespace facet { + namespace detail { + struct PlaceholderAbstractInterface { + virtual double foo(void * data) const = 0; + }; + + static_assert(sizeof(PlaceholderAbstractInterface) == sizeof(void*)); + } + + /** Concept: abstract interface requirements + * Use: when inheriting an abstract interface + * (see also valid_abstract_interface() below) + **/ + template + concept abstract_facet = requires { + std::is_abstract_v, + std::is_polymorphic_v; + /** require no state, just a single vtable pointer **/ + sizeof(T) == sizeof(detail::PlaceholderAbstractInterface); + !std::has_virtual_destructor_v; + std::is_trivially_destructible_v; + }; + + /** Use: when defining an abstract facet AMyFacet + * + * struct AMyFacet { + * virtual void foo(void * data) const = 0; + * }; + * + * static_assert(valid_abstract_facet()); + * + **/ + template + consteval bool valid_abstract_facet() + { + static_assert + (std::is_abstract_v, + "Abstract facet is expected to have all-abstract methods"); + static_assert + (std::is_polymorphic_v, + "Abstract facet is expected to have vtable"); + static_assert + (sizeof(T) == sizeof(detail::PlaceholderAbstractInterface), + "Abstract facet is expected to have no state except for a single vtable pointer"); + static_assert + (!std::has_virtual_destructor_v, + "Abstract facet does not benefit from virtual dtor since no state"); + static_assert + (std::is_trivially_destructible_v, + "Abstract facet expected to have trivial dtor since no state"); + return true; + }; + + } /*namespace facet*/ +} /*namespace xo*/ + +/* end AFacet.hpp **/ diff --git a/xo-facet/utest/facet_utest_main.cpp b/xo-facet/utest/facet_utest_main.cpp new file mode 100644 index 00000000..fc7126f1 --- /dev/null +++ b/xo-facet/utest/facet_utest_main.cpp @@ -0,0 +1,6 @@ +/* file facet_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end facet_utest_main.cpp */ diff --git a/xo-facet/utest/objectmodel.test.cpp b/xo-facet/utest/objectmodel.test.cpp new file mode 100644 index 00000000..1dd0ece3 --- /dev/null +++ b/xo-facet/utest/objectmodel.test.cpp @@ -0,0 +1,61 @@ +/** @file objectmodel.test.cpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#include "xo/facet/AFacet.hpp" +#include +#include +#include +#include + +namespace xo { + using xo::facet::valid_abstract_facet; + + namespace ut { + // ------ AComplex ----- + + /** abstract interface for a complex number **/ + struct AComplex { + using TypeErasedIface = struct IComplex_Any; + + 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; + + private: + static bool _valid; + }; + + bool + AComplex::_valid = valid_abstract_facet(); + + // ----- 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 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); } + + private: + static bool _valid; + }; + + bool + IComplex_Any::_valid = true; //valid_interface_implementation(); + + + } +} + +/* end objectmodel.test.cpp */