diff --git a/xo-alloc2/README.md b/xo-alloc2/README.md index 6d867acf..465172d6 100644 --- a/xo-alloc2/README.md +++ b/xo-alloc2/README.md @@ -3,13 +3,15 @@ # Relative to xo-alloc: 1. keep interface and data separate. - 1a. `Representation` classes. Entirely passive; strictly no methods. + 1a. *Representation* or *Data* classes. Entirely passive; strictly no methods. motivation: data doesn't carry any linker-dependency baggage; it's just layout. example: - struct RPolar { double arg; double mag; }; - struct RRect { double x; double y; }; +``` + struct DPolar { double arg; double mag; }; + struct DRRect { double x; double y; }; +``` 1b. `Interface` classes. These have abstract methods only. motivation: for runtime polymorphism, specify interface @@ -18,7 +20,8 @@ as first argument. example: - struct IComplex { +``` + struct AComplex { using repr_type = void; virtual double xcoord(void * repr) const = 0; @@ -26,6 +29,7 @@ virtual double magnitude(void * repr) const = 0; virtual double argument(void * repr) const = 0; }; +``` 1c. `Implementation` classes. Implement a specific interface (as in 1b) for a specific data representation (as in 1a). @@ -36,7 +40,7 @@ example: ``` - struct Complex_Rect { + struct IComplex_Rect : public AComplex { using repr_type = RRect; double _xcoord(RRect * repr) const { return repr->x; } @@ -60,7 +64,7 @@ double argument(void * repr) const final override; }; - struct Complex_Polar { + struct IComplex_Polar : public AComplex { using repr_type = RPolar; // implement IComplex for RPolar @@ -68,70 +72,111 @@ }; ``` + Here `IComplex_Rect` and `IComplex_Polar` are constructible. + They're concrete in the sense that they expect a specific representation + (`IComplex_Rect::repr_type`, `IComplex_Polar::repr_type` respectively). + 1d. `Object` classes. Pair implementation and interface. May use smart pointer here to express strategy for managing memory used for representation. Don't expect to need this for interfaces, since interface content entirely known at compile time. example: +``` // borrowed - struct _Complex_Rect : public Complex_Rect { - bp repr; // naked pointer + struct OComplex_Rect : public IComplex_Rect { + DRect * data() const { return data_; } + + bp data_; // naked pointer }; - struct _Complex_Polar : public Complex_Polar { - bp repr; + struct OComplex_Polar : public IComplex_Polar { + DPolar * data() const { return data_; } + + bp data_; }; // unique - struct _Complex_Rect : public Complex_Rect { - up repr; // unique_ptr + struct OComplex_Rect : public IComplex_Rect { + DRect * data() const { return data_; } + + up data_; // unique_ptr }; .. - +``` Can do this generically. +``` + // in bx: 'b' short for 'borrowed' as in unowned. + // 'x' just to distinguish from 'pointer'. + // template - struct bxp : public Iface { + struct bx : public Iface { + explicit bx(Repr * data) : data_{data} {} + Repr * data() const { return data_; } + bp data_; }; - using t1 = bxp; - using t2 = bxp; - - etc. + DRect z1_data{1.0, -1.0}; + bx z1{&z1_data}; + DPolar z2_data{sqrt(2.0), pi * 8/7}; + bx z2{&z2_data}; +``` Then to invoke a method (compile-time polymorphism) - - bxp obj; - obj.xcoord(obj.data_); // obj.xcoord() - - Or for runtime polymorphism - - bxp obj; - obj.xcoord(obj.data_); // obj.xcoord() +``` + z1._xcoord(z1.data()); +``` 1e. Runtime polymorphism - Observe that bxp and bxp have the same top-level representation. - - Both have iface member that inherits IComplex, - both have data pointer compatible with their respective iface member - Can have common representation for runtime polymorphism - - `bxp` and `bxp` have the same size and compatible representation. - both inherit `IComplex` + - safe to reinterpret cast +``` + // type-erased (placeholder, never used) + struct IComplex_Any : public AComplex { + using repr_type = void; - - safe to reinterpret cast to + double xcoord(void * repr) const final override { assert(false); return 0.0; } + }; + + bx z1 = ...; + bx z1_any = reinterpret_cast(z1); +``` + Capturing the pattern: +``` + // in abstract interface + struct AComplex { + using ErasedIfaceType = IComplex_Any; + .. + } + + template + struct bx : public Iface { + .. + operator bx() { + // in particular, overwrites vtable pointer + return reinterpret_cast>(*this); + } + .. + }; + ``` 2. Remarks - shared pattern with pimpl idiom, except impl isn't private + - can use the same Data type with an unrelated interface. + Although lose the automatic assocation - can put forwarding methods into object structs, though will be boilerplatey. diff --git a/xo-alloc2/utest/CMakeLists.txt b/xo-alloc2/utest/CMakeLists.txt new file mode 100644 index 00000000..0983d6e1 --- /dev/null +++ b/xo-alloc2/utest/CMakeLists.txt @@ -0,0 +1,14 @@ +# xo-alloc2/utest/CMakeLists.txt +# + +set(UTEST_EXE utest.alloc2) +set(UTEST_SRCS + alloc2_utest_main.cpp + objectmodel.test.cpp) + +if (ENABLE_TESTING) + xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS}) + xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2) +endif() + +# end CMakeLists.txt diff --git a/xo-alloc2/utest/alloc2_utest_main.cpp b/xo-alloc2/utest/alloc2_utest_main.cpp new file mode 100644 index 00000000..02d64a23 --- /dev/null +++ b/xo-alloc2/utest/alloc2_utest_main.cpp @@ -0,0 +1,6 @@ +/* file alloc2_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end alloc2_utest_main.cpp */ diff --git a/xo-alloc2/utest/objectmodel.test.cpp b/xo-alloc2/utest/objectmodel.test.cpp new file mode 100644 index 00000000..5eb266d6 --- /dev/null +++ b/xo-alloc2/utest/objectmodel.test.cpp @@ -0,0 +1,202 @@ +/** @file objectmodel.test.cpp + * + * @author: Roland Conybeare, Dec 2025 + * + * Testing rust-like split iface/data object model + * See xo-alloc2/README.md + * + * Ingredients: + * 1. abstract interface: all virtual methods. No assumptions about representation. + * No state (besides implict vtable pointer) + * + * Rules: + * 1. abstract interface must have no state besides implicit vtable pointer. + * This is a strongly-held principle, we're keeping data representation entirely + * separate + * 2. representations as passive as possible. No getters. All public members. + * Exceptions to this principle: + * - ctors (including copy/move ctors, when needed) + * - dtors + * + * Conventions: + * 1. abstract interface start with letter A, e.g. AComplex + * 2. representation struct names follow pattern DRepr, e.g. DPolar, DRect. + * Don't require "intended primary interface" in the name, + * since we're seeking ability to attach the same data to different interfaces + **/ + +#include +#include +#include + +namespace xo { + namespace ut { + namespace { + /** Associates an interface with an representation. + * Specialize to record such associations. + **/ + + template + struct ISpecificFor; + + /** type-erased implementation of AComplex, see below **/ + struct IComplex_Any; + + /** abstract interface for a complex number **/ + struct AComplex { + using TypeErasedIface = 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; + }; + + /** 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; } + }; + + template + struct IComplex_Specific : public AComplex { + double _xcoord(Repr *) const; + double _ycoord(Repr *) const; + double _argument(Repr *) const; + double _magnitude(Repr *) const; + + virtual double xcoord(void * data) const final override { return _xcoord((Repr*)data); } + virtual double ycoord(void * data) const final override { return _ycoord((Repr*)data); } + virtual double argument(void * data) const final override { return _argument((Repr*)data); } + virtual double magnitude(void * data) const final override { return _magnitude((Repr*)data); } + }; + + // ----- Polar Coordinates ----- + + /** complex number, represented using polar coordinates **/ + struct DPolarCoords { + DPolarCoords(double arg, double mag) : arg_{arg}, mag_{mag} {} + + double arg_; + double mag_; + }; + + /** implementation of AComplex interface with representation DPolarCoords **/ + using struct IComplex_DPolarCoords = IComplex_Specific; + + template <> + IComplex_Specific::_xcoord(DPolarCoords * data) const { + return data->mag_ * std::cos(data->arg_); + }; + + template <> + IComplex_Specific::_ycoord(DPolarCoords * data) const { + return data->mag_ * std::sin(data->arg_); + }; + + template <> + IComplex_Specific::_argument(DPolarCoords * data) const { + return data->arg_; + } + + template <> + IComplex_Specific::_magnitude(DPolarCoords * data) const { + return data->mag_; + } + + template <> + struct ISpecificFor { + using ImplType = IComplex_Specific; + }; + + // ----- Rectangular Coordinates ----- + + /** complex number, represented using rectangular coordinates **/ + struct DRectCoords { + DRectCoords(double x, double y) : x_{x}, y_{y} {} + + double x_; + double y_; + }; + + /** implementation of AComplex interface with representation DRectCoords **/ + using struct IComplex_DRectCoords = IComplex_Specific; + + template <> + IComplex_Specific::_xcoord(DRectCoords * data) const { + return data->mag_ * std::cos(data->arg_); + }; + + template <> + IComplex_Specific::_ycoord(DRectCoords * data) const { + return data->mag_ * std::sin(data->arg_); + }; + + template <> + IComplex_Specific::_argument(DRectCoords * data) const { + return data->arg_; + } + + template <> + IComplex_Specific::_magnitude(DRectCoords * data) const { + return data->mag_; + } + + template <> + struct ISpecificFor { + using ImplType = IComplex_Specific; + }; + + template <> + struct ISpecificFor { + using ImplType = IComplex_Specific; + }; + + // ----- box with unique pointer ----- + + /** u for unique, b for box. Using lowercase for unobtrusiveness, + * so that in ub, MyType is naturally emphasized + * + * @tparam ISpecific will be a specific interface, + * such as ISpecificFor + * + * Example: + * OUniqueBox z1 = ..; + * z1._xcoord(z1.data()); + **/ + template + struct OUniqueBox : ISpecificFor::typename ImplType { + Data * data() const { return data_.get(); } + + up data_; + }; + + template + struct RComplex : public Object { + double xcoord() const { return _xcoord(data()); } + double ycoord() const { return _ycoord(data()); } + double argument() const { return _argument(data()); } + double magnitude() const { return _magnitude(data()); } + } + + template + struct RoutingFor; + + template + struct RoutingFor { + using RoutingType = RComplex; + }; + + template + struct ubox : public RoutingFor::typename RoutingType { } + } + + } + } +}