xo-alloc/xo-facet/utest/objectmodel.test.cpp

405 lines
13 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/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::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<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 FacetRttiShim<AComplex>
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 <typename DRepr>
int32_t
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 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<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)} {}
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 <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>(&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 = 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);
}
}
}
/* end objectmodel.test.cpp */