493 lines
16 KiB
C++
493 lines
16 KiB
C++
/** @file objectmodel.test.cpp
|
|
*
|
|
* @author Roland Conybeare, Dec 2025
|
|
**/
|
|
|
|
#include "xo/facet/facet.hpp"
|
|
#include "xo/facet/facet_implementation.hpp"
|
|
#include "xo/facet/FacetRegistry.hpp"
|
|
#include "xo/facet/OObject.hpp"
|
|
#include "xo/facet/RRouter.hpp"
|
|
#include "xo/facet/typeseq.hpp"
|
|
#include "xo/facet/obj.hpp"
|
|
#include <catch2/catch.hpp>
|
|
#include <cmath>
|
|
#include <numbers>
|
|
#include <cassert>
|
|
#include <cstring>
|
|
|
|
namespace xo {
|
|
using xo::facet::valid_abstract_facet;
|
|
using xo::facet::valid_facet_implementation;
|
|
using xo::facet::FacetImplementation;
|
|
using xo::facet::FacetRegistry;
|
|
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 typeseq _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<AComplex>();
|
|
|
|
// ----- IComplex_Impl -----
|
|
|
|
template <typename DRepr>
|
|
struct IComplex_Impl;
|
|
|
|
template <typename DRepr>
|
|
struct IComplex_Xfer : public AComplex {
|
|
// parallel interface to AComplex, but with specific data type
|
|
using Impl = IComplex_Impl<DRepr>;
|
|
|
|
// from AComplex
|
|
|
|
virtual typeseq _typeseq() const final override { return s_typeseq; }
|
|
|
|
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 typeseq s_typeseq;
|
|
static bool _valid;
|
|
};
|
|
|
|
template <typename DRepr>
|
|
typeseq
|
|
IComplex_Xfer<DRepr>::s_typeseq = typeseq::id<DRepr>();
|
|
|
|
template <typename DRepr>
|
|
bool
|
|
IComplex_Xfer<DRepr>::_valid = valid_facet_implementation<AComplex, IComplex_Xfer>;
|
|
|
|
namespace facet {
|
|
template <typename DRepr>
|
|
struct FacetImplementation<AComplex, DRepr> {
|
|
using ImplType = IComplex_Xfer<DRepr>;
|
|
};
|
|
}
|
|
|
|
// ----- 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 typeseq _typeseq() const final override { 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 typeseq s_typeseq;
|
|
static bool _valid;
|
|
};
|
|
|
|
typeseq
|
|
IComplex_Any::s_typeseq = typeseq::id<DVariantPlaceholder>();
|
|
|
|
bool
|
|
IComplex_Any::_valid = valid_facet_implementation<AComplex, IComplex_Any>();
|
|
|
|
namespace facet {
|
|
template <>
|
|
struct FacetImplementation<AComplex, DVariantPlaceholder> {
|
|
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<DPolarCoords>;
|
|
|
|
template <>
|
|
struct IComplex_Impl<DPolarCoords> {
|
|
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<DRectCoords>;
|
|
|
|
template <>
|
|
struct IComplex_Impl<DRectCoords> {
|
|
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 <typename Object>
|
|
struct RComplex : public Object {
|
|
using ObjectType = Object;
|
|
|
|
RComplex() {}
|
|
RComplex(Object::DataPtr data) : Object{std::move(data)} {}
|
|
|
|
typeseq _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 <typename Object>
|
|
bool
|
|
RComplex<Object>::_valid = valid_object_router<Object>();
|
|
|
|
namespace facet {
|
|
template <typename Object>
|
|
struct RoutingFor<AComplex, Object> {
|
|
using RoutingType = RComplex<Object>;
|
|
};
|
|
} /*namespace facet*/
|
|
|
|
namespace ut {
|
|
// ----- TESTS -----
|
|
|
|
TEST_CASE("facet-1", "[facet]")
|
|
{
|
|
// AComplex passes abstract facet checks
|
|
REQUIRE(AComplex::_valid);
|
|
|
|
// IComplex_Any passes facet implementation checks
|
|
REQUIRE(IComplex_Any::_valid);
|
|
}
|
|
|
|
TEST_CASE("xfer-polar-1", "[facet]")
|
|
{
|
|
IComplex_Impl<DPolarCoords> impl;
|
|
DPolarCoords z1{0.0, 1.0};
|
|
|
|
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);
|
|
}
|
|
|
|
TEST_CASE("xfer-rect-1", "[facet]")
|
|
{
|
|
IComplex_Impl<DRectCoords> impl;
|
|
DRectCoords z1{1.0, 0.0};
|
|
|
|
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);
|
|
}
|
|
|
|
TEST_CASE("oobject-polar-1", "[facet]")
|
|
{
|
|
using Object = OObject<AComplex, DPolarCoords>;
|
|
|
|
DPolarCoords z1{0.0, 1.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("oobject-rect-1", "[facet]")
|
|
{
|
|
using Object = OObject<AComplex, DRectCoords>;
|
|
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<AComplex, OObject<AComplex, DPolarCoords>>;
|
|
|
|
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<AComplex, OObject<AComplex, DRectCoords>>;
|
|
|
|
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<AComplex, OObject<AComplex>>;
|
|
|
|
// 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<DPolarCoords>() == nullptr);
|
|
REQUIRE(var1.downcast<DRectCoords>() == &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<DRectCoords>() == nullptr);
|
|
REQUIRE(var1.downcast<DPolarCoords>() == &z2);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("obj-rect-1", "[facet]")
|
|
{
|
|
DRectCoords z1{1.0, 0.0};
|
|
auto z1o = with_facet<AComplex>::mkobj(&z1);
|
|
|
|
static_assert(std::is_same_v<decltype(z1o)::FacetType, AComplex>);
|
|
static_assert(std::is_same_v<decltype(z1o)::DataType, DRectCoords>);
|
|
|
|
REQUIRE(z1o._typeseq() == typeseq::id<DRectCoords>());
|
|
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<DRectCoords>() == &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.data() = 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<AComplex> var1;
|
|
|
|
REQUIRE(!var1);
|
|
REQUIRE(var1.iface() != nullptr);
|
|
REQUIRE(var1.data() == nullptr);
|
|
|
|
DRectCoords z1{1.0, 0.0};
|
|
obj<AComplex, DRectCoords> z1o(&z1);
|
|
|
|
REQUIRE(z1o);
|
|
}
|
|
|
|
TEST_CASE("registry-1", "[facet][registry]")
|
|
{
|
|
auto & registry = FacetRegistry::instance();
|
|
|
|
// register implementations
|
|
FacetRegistry::register_impl<AComplex, DRectCoords>();
|
|
FacetRegistry::register_impl<AComplex, DPolarCoords>();
|
|
|
|
REQUIRE(registry.contains(typeseq::id<AComplex>(),
|
|
typeseq::id<DRectCoords>()));
|
|
REQUIRE(registry.contains(typeseq::id<AComplex>(),
|
|
typeseq::id<DPolarCoords>()));
|
|
}
|
|
|
|
TEST_CASE("registry-lookup-1", "[facet][registry]")
|
|
{
|
|
// ensure registered
|
|
FacetRegistry::register_impl<AComplex, DRectCoords>();
|
|
FacetRegistry::register_impl<AComplex, DPolarCoords>();
|
|
|
|
// runtime lookup using typeseq
|
|
typeseq rect_id = typeseq::id<DRectCoords>();
|
|
typeseq polar_id = typeseq::id<DPolarCoords>();
|
|
|
|
const AComplex * rect_impl = FacetRegistry::impl_for<AComplex>(rect_id);
|
|
const AComplex * polar_impl = FacetRegistry::impl_for<AComplex>(polar_id);
|
|
|
|
REQUIRE( rect_impl != nullptr);
|
|
REQUIRE(polar_impl != nullptr);
|
|
|
|
// use implementations
|
|
DRectCoords z1{1.0, 0.0};
|
|
DPolarCoords z2{0.0, 1.0};
|
|
|
|
REQUIRE(rect_impl->xcoord(&z1) == 1.0);
|
|
REQUIRE(rect_impl->ycoord(&z1) == 0.0);
|
|
|
|
REQUIRE(polar_impl->xcoord(&z2) == 1.0);
|
|
REQUIRE(polar_impl->ycoord(&z2) == 0.0);
|
|
}
|
|
|
|
TEST_CASE("registry-cross-facet", "[facet][registry]")
|
|
{
|
|
// simulate the DList::pretty() use case:
|
|
// given obj<AComplex> with unknown repr type,
|
|
// look up implementation at runtime
|
|
|
|
FacetRegistry::register_impl<AComplex, DRectCoords>();
|
|
FacetRegistry::register_impl<AComplex, DPolarCoords>();
|
|
|
|
// create type-erased objects
|
|
DRectCoords z1{1.0, 0.0};
|
|
DPolarCoords z2{0.0, 2.0};
|
|
|
|
obj<AComplex, DRectCoords> obj1(&z1);
|
|
obj<AComplex, DPolarCoords> obj2(&z2);
|
|
|
|
// simulate: only know typeseq at runtime
|
|
typeseq repr1 = obj1._typeseq();
|
|
typeseq repr2 = obj2._typeseq();
|
|
|
|
// lookup implementations
|
|
const AComplex * impl1 = FacetRegistry::impl_for<AComplex>(repr1);
|
|
const AComplex * impl2 = FacetRegistry::impl_for<AComplex>(repr2);
|
|
|
|
REQUIRE(impl1 != nullptr);
|
|
REQUIRE(impl2 != nullptr);
|
|
|
|
// use via runtime-looked-up implementation
|
|
REQUIRE(impl1->magnitude(obj1.data()) == 1.0);
|
|
REQUIRE(impl2->magnitude(obj2.data()) == 2.0);
|
|
}
|
|
|
|
TEST_CASE("registry-not-found", "[facet][registry]")
|
|
{
|
|
// lookup for unregistered type returns nullptr
|
|
struct DUnknown {};
|
|
|
|
const AComplex * impl = FacetRegistry::impl_for<AComplex>(typeseq::id<DUnknown>());
|
|
|
|
REQUIRE(impl == nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* end objectmodel.test.cpp */
|