xo-facet: header impl + docs
This commit is contained in:
parent
85d4f0ee15
commit
ab7b71433e
13 changed files with 1111 additions and 33 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,3 +6,6 @@ Glossary
|
|||
.. glossary::
|
||||
fomo
|
||||
| facet object model
|
||||
|
||||
xfer
|
||||
| abbreviation for transfer
|
||||
|
|
|
|||
93
docs/implementation.rst
Normal file
93
docs/implementation.rst
Normal file
|
|
@ -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<A,D>
|
||||
- x.foo()
|
||||
- convenience wrapper with interface A, with state D*
|
||||
* - RRouter<A,D>
|
||||
- x.foo()
|
||||
- auto injects data pointer
|
||||
* - OObject<A,D>
|
||||
- x.iface()->foo(x.data(), ..)
|
||||
- fat object pointer. combine i/face + data pointer.
|
||||
* - FacetImplementationType<A,D>
|
||||
- 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<<obj>>
|
||||
z1 : iface = vt1
|
||||
z1 : data = d1
|
||||
|
||||
object vt1<<interface>>
|
||||
vt1 : foo()
|
||||
vt1 : bar()
|
||||
|
||||
object d1<<data>>
|
||||
d1 : x = 0.6
|
||||
d1 : y = 0.8
|
||||
|
||||
z1 o-- vt1
|
||||
z1 o-- d1
|
||||
|
|
@ -49,6 +49,7 @@ This gives us several benefits:
|
|||
:maxdepth: 2
|
||||
:caption: xo-facet contents
|
||||
|
||||
implementation
|
||||
glossary
|
||||
genindex
|
||||
search
|
||||
|
|
|
|||
193
include/xo/facet/OObject.hpp
Normal file
193
include/xo/facet/OObject.hpp
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
/** @file OObject.hpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "facet_implementation.hpp"
|
||||
#include "typeseq.hpp"
|
||||
#include <new>
|
||||
#include <cstring>
|
||||
|
||||
namespace xo {
|
||||
namespace facet {
|
||||
template <typename OObject>
|
||||
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::AbstractInterface, OObject::ISpecific>,
|
||||
"OObject::ISpecific must implement Object::AbstractInterface");
|
||||
static_assert(std::is_standard_layout_v<OObject>,
|
||||
"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<const typename OObject::AbstractInterface*>; },
|
||||
"OObject must have non-virtual method iface()"
|
||||
" returning const OObject::AbstractInterface");
|
||||
static_assert(requires(const OObject & obj) {
|
||||
{ obj.data() } -> std::convertible_to<typename OObject::DataType*>; },
|
||||
"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 <typename AFacet, typename DRepr = DVariantPlaceholder>
|
||||
struct OObject {
|
||||
using FacetType = AFacet;
|
||||
using ISpecific = FacetImplType<AFacet, DRepr>;
|
||||
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<DataType, DVariantPlaceholder>
|
||||
{
|
||||
/* 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<DataType, DVariantPlaceholder>)
|
||||
{
|
||||
/* 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 <typename DOther>
|
||||
OObject & from_data(DOther * other) {
|
||||
static_assert(std::is_same_v<DRepr, DVariantPlaceholder>
|
||||
|| std::is_convertible_v<DOther*, DRepr*>);
|
||||
|
||||
if constexpr (std::is_convertible_v<DOther*, DRepr*>) {
|
||||
/** assigning from data with same representation **/
|
||||
this->data_ = other;
|
||||
} else /*DRepr is DVariantPlaceholder*/ {
|
||||
/** assigning to variant **/
|
||||
|
||||
/* acquire fat object pointer for (AFacet, DOther) */
|
||||
OObject<AFacet, DOther> 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 <typename DOther>
|
||||
DOther * downcast()
|
||||
requires (std::is_same_v<DataType, DVariantPlaceholder>)
|
||||
{
|
||||
if (data_ && (typeseq::id<DOther>() == this->iface()->_typeseq())) {
|
||||
/* actual runtime type for data_ is DOther,
|
||||
* safe to reinterpret
|
||||
*/
|
||||
return reinterpret_cast<DOther*>(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 <typename AFacet, typename DRepr>
|
||||
bool
|
||||
OObject<AFacet, DRepr>::_valid = valid_object_traits<OObject>();
|
||||
|
||||
} /*namespace facet*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end OObject.hpp */
|
||||
74
include/xo/facet/OUniqueBox.hpp
Normal file
74
include/xo/facet/OUniqueBox.hpp
Normal file
|
|
@ -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<AInterface, ..> 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 <typename AInterface, typename Data = DOpaquePlaceholder>
|
||||
struct OUniqueBox {
|
||||
using AbstractInterface = AInterface;
|
||||
using ISpecific = ISpecificFor<AInterface, Data>::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<Data, DOpaquePlaceholder>
|
||||
{
|
||||
return std::launder(&iface_);
|
||||
}
|
||||
|
||||
const AInterface * iface() const
|
||||
requires (!std::is_same_v<Data, DOpaquePlaceholder>)
|
||||
{
|
||||
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<OUniqueBox> **/
|
||||
Data * data() const { return data_; }
|
||||
|
||||
ISpecific iface_;
|
||||
DataBox data_ = nullptr;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end OUniqueBox.hpp */
|
||||
51
include/xo/facet/RRouter.hpp
Normal file
51
include/xo/facet/RRouter.hpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/** @file RRouter.hpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OObject.hpp"
|
||||
|
||||
namespace xo {
|
||||
namespace facet {
|
||||
template <typename RRouter>
|
||||
consteval bool valid_object_router()
|
||||
{
|
||||
static_assert(requires { typename RRouter::ObjectType; },
|
||||
"Router type must provide typename Router::ObjectType");
|
||||
static_assert(valid_object_traits<RRouter::ObjectType>,
|
||||
"Router::ObjectType must satisfy objectmodel traits");
|
||||
static_assert(std::is_standard_layout_v<RRouter>,
|
||||
"Router must have standard laayout, i.e. no virtual methods."
|
||||
" Virtual methods belong in OObject::AbstractInterface*>");
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* template <typename Object>
|
||||
* 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 <typename Object>
|
||||
* struct RoutingFor<AMyFacet, Object> {
|
||||
* using RoutingType = RMyFacet<Object>;
|
||||
* };
|
||||
**/
|
||||
template <typename AFacet, typename Object>
|
||||
requires abstract_facet<AFacet>
|
||||
struct RoutingFor;
|
||||
|
||||
template <typename AFacet, typename Object>
|
||||
using RoutingType = RoutingFor<AFacet, Object>::RoutingType;
|
||||
}
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end RRouter.hpp */
|
||||
|
|
@ -7,10 +7,16 @@
|
|||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
#include <cstdint>
|
||||
|
||||
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<AMyFacet>());
|
||||
* bool AMyFacet::_valid = valid_abstract_facet<AMyFacet>();
|
||||
*
|
||||
**/
|
||||
template <typename T>
|
||||
|
|
@ -59,6 +68,10 @@ namespace xo {
|
|||
static_assert
|
||||
(std::is_trivially_destructible_v<T>,
|
||||
"Abstract facet expected to have trivial dtor since no state");
|
||||
static_assert
|
||||
(requires(const T & facet) {
|
||||
{ facet._typeseq() } -> std::convertible_to<std::int32_t>; },
|
||||
"Abstract facet must provide a _typeseq() method for safe downcasting");
|
||||
return true;
|
||||
};
|
||||
|
||||
122
include/xo/facet/facet_implementation.hpp
Normal file
122
include/xo/facet/facet_implementation.hpp
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/** @file facet_implementation.hpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
//#include "xo/facet/facet.hpp"
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
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 <typename AFacet, typename ISpecific>
|
||||
concept implements_facet = requires {
|
||||
std::is_base_of_v<AFacet, ISpecific>;
|
||||
std::is_default_constructible_v<ISpecific>;
|
||||
std::is_standard_layout_v<ISpecific>;
|
||||
/** require no additional state **/
|
||||
sizeof(ISpecific) == sizeof(AFacet);
|
||||
};
|
||||
|
||||
/** Use: when defining datatype recovery for a typed facet implementation:
|
||||
*
|
||||
* template <typename Repr>
|
||||
* 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 <typename Repr>
|
||||
* bool AMyFacet_Impl<Repr>::_valid
|
||||
* = valid_facet_implementation<AMyFacet, AMyFacet_Specific>();
|
||||
**/
|
||||
template <typename AFacet, typename ISpecific>
|
||||
consteval bool valid_facet_implementation()
|
||||
requires (valid_abstract_facet<AFacet>())
|
||||
{
|
||||
static_assert(std::is_base_of_v<AFacet, ISpecific>,
|
||||
"Facet implementation must inherit FacetRttiShim<AFacet>");
|
||||
static_assert(std::is_default_constructible_v<ISpecific>,
|
||||
"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<ISpecific>,
|
||||
"Facet implementation does not benefit from virtual dtor (since has no data)");
|
||||
static_assert(std::is_trivially_destructible_v<ISpecific>,
|
||||
"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<AFacet, ISpecific>,
|
||||
// "Interface implementation must directly inherit interface (no base offset)");
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/** Compile-time facet implementation lookup
|
||||
*
|
||||
* @c FacetImplementation<AMyFacet, DSomeRepr>::ImplType
|
||||
* gives the type that implements @c AMyFacet with state @c DSomeRepr
|
||||
*
|
||||
* template<DRepr>
|
||||
* struct FacetImplementation<AMyFacet, DRepr> {
|
||||
* using ImplType = IMyFacet_Impl<DRepr>;
|
||||
* };
|
||||
*
|
||||
* template<>
|
||||
* struct FacetImplementation<AMyFacet, DVariantPlaceholder> {
|
||||
* using ImplType = IMyFacet_Any;
|
||||
* };
|
||||
*
|
||||
**/
|
||||
template <typename AFacet, typename DRepr>
|
||||
struct FacetImplementation {};
|
||||
|
||||
/** Retrieve facet implementation for a (facet, datatype) pair **/
|
||||
template <typename AFacet, typename DRepr>
|
||||
using FacetImplType = FacetImplementation<AFacet, DRepr>::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<AMyFacet, IMyFacet_Any>();
|
||||
**/
|
||||
struct DVariantPlaceholder {};
|
||||
|
||||
/** PLAN:
|
||||
* expect also will need runtime version of FacetImplementation.
|
||||
**/
|
||||
|
||||
} /*namespace facet*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end facet_implementation.hpp */
|
||||
30
include/xo/facet/facet_rtti.hpp
Normal file
30
include/xo/facet/facet_rtti.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/** @file facet_rtti.hpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#include "facet.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
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 <typename AFacet>
|
||||
struct FacetRttiShim : public AFacet {
|
||||
};
|
||||
} /*namespace facet*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end facet_rtti.hpp */
|
||||
101
include/xo/facet/obj.hpp
Normal file
101
include/xo/facet/obj.hpp
Normal file
|
|
@ -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<DRectCoords> z1_in
|
||||
* = std::make_unique<DRectCoords>(1.0, 0.0):
|
||||
* ubox<AComplex> z1{z1_in.release()};
|
||||
* z1.xcoord();
|
||||
*
|
||||
*
|
||||
* +-----+ +-----------------+
|
||||
* Interface | x-------------->| vtable for |
|
||||
* +-----+ | some descendant |
|
||||
* Data | x--------\ | of AInterface |
|
||||
* +-----+ | | |
|
||||
* | +-----------------+
|
||||
* |
|
||||
* | +--------------+
|
||||
* \----->| data :: Repr |
|
||||
* +--------------+
|
||||
*
|
||||
* Binary representation of unay<AInterface, Data>
|
||||
* is compatible for different values of @tparam Data
|
||||
* as long as vtable pointer moves along with data pointer.
|
||||
*
|
||||
* In particular binary representation for
|
||||
* ubox<AInterface,D> is as if it inherited ubox<AInterface>
|
||||
* (even though it does not as far as compiler is concerned)
|
||||
*
|
||||
* This is load-bearing for @ref move2any see below
|
||||
**/
|
||||
template <typename AFacet, typename DRepr = DVariantPlaceholder>
|
||||
struct obj : public RoutingType<AFacet, OObject<AFacet, DRepr>> {
|
||||
using Super = RoutingType<AFacet, OObject<AFacet, DRepr>>;
|
||||
|
||||
obj() {}
|
||||
explicit obj(Super::DataPtr d) : Super(d) {}
|
||||
|
||||
/** copy constructor **/
|
||||
template <typename DOther>
|
||||
obj(const obj<AFacet, DOther> && other) : Super()
|
||||
{
|
||||
if constexpr (std::is_convertible_v<DRepr, DOther>) {
|
||||
this->data_ = other.data_;
|
||||
} else if constexpr (std::is_same_v<DRepr, DVariantPlaceholder>) {
|
||||
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 <typename DOther>
|
||||
obj(const obj<AFacet, DOther> && other)
|
||||
requires (std::is_same_v<DRepr, DVariantPlaceholder>
|
||||
|| std::is_convertible_v<DOther*, DRepr>)
|
||||
: Super()
|
||||
{
|
||||
static_assert(sizeof(obj<AFacet, DOther>)
|
||||
== sizeof(obj<AFacet, DRepr>));
|
||||
|
||||
other.move2any(this);
|
||||
|
||||
assert(other.data_ = nullptr);
|
||||
}
|
||||
|
||||
/** safe downcast from variant. null if downcast fails **/
|
||||
static obj from(const OObject<AFacet> & other) {
|
||||
return obj(other.template downcast<DRepr>());
|
||||
}
|
||||
};
|
||||
|
||||
template <typename AFacet, typename DRepr>
|
||||
inline obj<AFacet, DRepr>
|
||||
with_facet(DRepr * data) {
|
||||
return obj<AFacet, DRepr>(data);
|
||||
}
|
||||
} /*namespace facet*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end obj.hpp */
|
||||
53
include/xo/facet/typeseq.hpp
Normal file
53
include/xo/facet/typeseq.hpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/** @file typeseq.hpp
|
||||
*
|
||||
* @author Roland Conybeare, Dec 2025
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
namespace facet {
|
||||
/**
|
||||
* Tag here so we can preserve header-only implementation
|
||||
* and still have static variable
|
||||
*/
|
||||
template<typename Tag = class typeseq_tag>
|
||||
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 <typename T>
|
||||
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 <typename Tag>
|
||||
int32_t typeseq_impl<Tag>::s_next_id = 0;
|
||||
|
||||
using typeseq = typeseq_impl<>;
|
||||
}
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end typeseq.hpp */
|
||||
|
|
@ -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 <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 {
|
||||
// ------ 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<DPolarCoords> 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<AComplex>();
|
||||
TEST_CASE("xfer-rect-1", "[facet]")
|
||||
{
|
||||
IComplex_Impl<DRectCoords> 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<AComplex, DPolarCoords>;
|
||||
|
||||
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<AComplex, IComplex_Any>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue