xo-expression2/xo-facet/include/xo/facet/OObject.hpp

220 lines
8.6 KiB
C++

/** @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*;
using Opaque = void *;
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; }
// ----- iface() for variant fat pointer -----
/** 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_);
}
/** non-const verison. Technically all interface methods are const.
* But counterintuitive to have to mark interface methods const
* that are dedicated to mutable data.
**/
FacetType * iface()
requires std::is_same_v<DataType, DVariantPlaceholder>
{
return std::launder(&iface_);
}
// ----- iface() for typed fat pointer -----
/** 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_;
}
/** non-const verison. Technically all interface methods are const.
* But counterintuitive to have to mark interface methods const
* that are dedicated to mutable data.
**/
FacetType * iface()
requires(!std::is_same_v<DataType, DVariantPlaceholder>)
{
return &iface_;
}
DataPtr data() const { return data_; }
Opaque opaque_data() const { return data_; }
void reset() { data_ = nullptr; }
void reset_opaque(Opaque data) { data_ = (DataPtr)data; }
/**
* 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() const
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 */