OpenVariant.h
// OpenVariant.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2011
@import "IObject.h"
@import "ReflectionByteCode.h"
#include <algorithm>
#include <utility>
/*
Open variants
-------------
A variant (whether open or closed) is a union type over some given set of pure value
types. Therefore a variant also represents a pure value type completely divorced from
object identity.
A closed variant is defined using a $variant which explicitly specifies the types to union
over. An open variant is defined using openvariant<T> and instead allows for an unbounded
set of classes to be members of the variant. These are the classes that implement
interface T.
One could say that the only logical difference to a closed variant is "push versus pull" -
i.e. does the declaration of the variant list the members, or do the members do that
instead?
Physically the implementation is quite different. A $variant uses a 32 bit tag and a
C++ union, and therefore doesn't normally involve any heap allocation. By contrast an
openvariant<T> is essentially a ptr<T> to a privately owned heap allocated object that
implements interface T, although semantically it represents the value pointed to by the
pointer, rather than the pointer itself. That changes everything!
In this implementation of an open variant, it is assumed that we union over reflected
classes with fully defined TypeOps.
An open variant is a pure value type, not a pointer
---------------------------------------------------
- Unlike a ptr<T> or pref<T> an openvariant<T> logically doesn't represent a pointer
and therefore isn't concerned with object identity.
- When a pref<T> is serialised, an OID is serialised, whereas when an openvariant<T>
is serialised a pure value is serialised.
- When ptr<T> or pref<T>s are compared, an identity test is performed (i.e. it tests
for whether they point at the same object). When openvariant<T>s are compared,
the underlying values are compared.
- ptr<T> or pref<T> can only be assigned with the address of an object.
openvariant<T> can only be assigned with a value.
- openvariant<T> can appear in models and open or closed variants in arbitrary ways.
ptr<T> cannot be placed in persistent models at all
pref<T> can only appear in persistent models in restricted ways (i.e. according to the
rules for where nodes of the UT can appear).
- openvariant<T> exclusively owns the value in it.
Serialisation
-------------
It is not assumed that the object in the openvariant implements IPersistable, because that
would create a dependency on cxPersistStore, and in any case IPersistable is concerned with
objects that have an OID in the database, and open variants are only concerned with
recording pure values, not with object identity. Rather, the ceda framework can serialise
an instance of a ReflectedClass if it is assumed the serialiseArrayFn and deserialiseArrayFn
TypeOps functions are available for serialisation of a value to an archive.
When an open variant is serialised the implementation first serialises some kind of type
identifier (perhaps a variable length PersistStore CID - which often serialises as a
single byte). This is analogous to a closed variant which first serialises an integer
tag. When the open variant is deserialised an object is dynamically created based on
the type identifier. On disk open variants are finer grained than IPersistable objects
in the sense that a single IPersistable object could serialise any number of open variants
within it.
Lifetime management
-------------------
There are three possible approaches:
1) openvariant<T> owns the object in it. It is not registered with the GC, so new not
$new is used to allocate it. Destroy() is used to delete it from ~openvariant<T>().
When a openvariant<T> is copy constructed or assigned, it is necessary to copy the
underlying object. Therefore it is required that the TypeOps have the
copyConstructArrayFn and assignArrayFn functions defined.
2) openvariant<T> treats the object instance as immutable and employs reference counting
on it. Interlocked increment/decrement functions are required to support
multithreading.
3) openvariant<T> treats the object instance as immutable and uses the tracing GC for
object cleanup. This imposes a limitation that openvariants cannot be copied or
assigned across CSpace boundaries.
We choose option 1.
Reflection of an open variant
-----------------------------
Unfortunatly an openvariant is a template class and we don't have support for reflection
of templates. This suggests that we need to introduce a special byte code FT_OPENVARIANT
which is similar to FT_INTERFACE_POINTER. Alternatively we introduce support for template
classes now.
As far as OT is concerned, a FT_OPENVARIANT is a primitive type like FT_INT32 that supports
assignment semantics. When an operation is generated, OCB_BeforeAssignValue/OCB_AfterAssignValueWithPrefs
is called, and parameterised by
struct FieldParams
{
ptr<IObject> obj;
PseudoFieldId fid;
void* addr;
};
So byte code is irrelevent here.
When writing a delta, AssignDeltaWriter::WriteNextDelta() uses a PathNavigatorBC to navigate
to the field, and a ReflectionByteCode describes the type of the field. The function call
SerialiseReflectedVariable(ar, rbc, field) is used to serialise the field of type 'rbc'.
Serialisation of a openvariant<T> is independent of T if and only if it records a
ptr<IObject> or ptr<IPersistable> rather than a ptr<T>. But then clients have to put up
with qicasts all the time. A pref<T> works this way (prefbase stores a ptr<IPersistable>)
but that is more easily justified because clients already expect operator*() on a pref<T>
to be a bit expensive.
So decision to make: Does openvariant<T> store a ptr<T> or a ptr<IObject>? We choose
ptr<T>.
*/
namespace ceda
{
// These functions assume the given object(s) support reflection - i.e. GetReflectedClass()
// doesn't return nullptr.
@api void IObject_Delete(ptr<IObject> p);
@api ptr<IObject> IObject_Clone(ptr<const IObject> p1);
@api bool IObject_LessThan(ptr<const IObject> p1, ptr<const IObject> p2);
@api bool IObject_Equal(ptr<const IObject> p1, ptr<const IObject> p2);
@api void IObject_Serialise(Archive& ar, ptr<const IObject> p);
@api void IObject_Deserialise(InputArchive& ar, ptr<IObject>& p);
@api void IObject_Write(xostream& os, ptr<const IObject> p);
struct IObjectVisitor;
///////////////////////////////////////////////////////////////////////////////////////////////////
// openvariant
/*
T is an interface that must be a subtype of IObject (this is so that it is safe to reinterpret
a ptr<T> as a ptr<IObject>
*/
template <typename T>
class openvariant
{
public:
// Implement copy ctor,
// operator=(), operator==() etc according to
// dereference semantic
~openvariant()
{
IObject_Delete(m_ptr);
}
openvariant() {}
template<typename V>
openvariant(const V& v)
{
m_ptr = new V(v);
}
// This appeats to be redundant w.r.t. the templatised copy ctor below, but in fact
// this is required to prevent the compiler generated copy ctor from being used.
openvariant(const openvariant& r) :
m_ptr( r.clone() )
{
}
// This allows for upcasts in the expected way.
template<typename T2>
openvariant(const openvariant<T2>& r) :
m_ptr( r.clone() )
{
}
openvariant& operator=(const openvariant& r)
{
// Use the copy and swap idiom
openvariant temp(r);
temp.swap(*this); // Non throwing swap
return *this;
}
void swap(openvariant& r)
{
using std::swap;
swap(m_ptr, r.m_ptr);
}
bool empty() const { return m_ptr == null; }
const T& get() const { return *m_ptr; }
//ptr<T> getptr() const { return m_ptr; }
bool _Lt(const openvariant& r) const
{
return IObject_LessThan(m_ptr, r.m_ptr);
}
bool _Eq(const openvariant& r) const
{
return IObject_Equal(m_ptr, r.m_ptr);
}
template<typename Archive>
void Serialise(Archive& ar) const
{
IObject_Serialise(ar, m_ptr);
}
template<typename Archive>
void Deserialise(Archive& ar)
{
ptr<IObject> p;
IObject_Deserialise(ar,p);
if (m_ptr)
{
m_ptr->Destroy();
}
m_ptr = qicast<T>(p);
}
void Write(xostream& os) const
{
IObject_Write(os, m_ptr);
}
// Returns a heap allocated clone of this openvariant
ptr<T> clone() const
{
ptr<IObject> c = IObject_Clone(m_ptr);
return (ptr<T>&) c;
}
private:
ptr<T> m_ptr;
};
template <typename T>
inline bool operator==(const openvariant<T>& x, const openvariant<T>& y) { return x._Eq(y); }
template <typename T>
inline bool operator!=(const openvariant<T>& x, const openvariant<T>& y) { return !(x == y); }
template <typename T>
inline bool operator<(const openvariant<T>& x, const openvariant<T>& y) { return x._Lt(y); }
template <typename T>
inline bool operator>(const openvariant<T>& x, const openvariant<T>& y) { return y < x; }
template <typename T>
inline bool operator<=(const openvariant<T>& x, const openvariant<T>& y) { return !(y < x); }
template <typename T>
inline bool operator>=(const openvariant<T>& x, const openvariant<T>& y) { return !(x < y); }
template <typename T>
ReflectionByteCode _GetReflected(const openvariant<const T>*)
{
@if (CEDA_ENABLE_STRING_TABLES)
{
static octet_t bc[] = { FT_OPEN_VARIANT, FT_CONST, FT_INTERFACE, 0x00, 0x00 };
return ReflectionByteCode(bc,&GetReflectedInterface<T>().name);
}
@else
{
static_assert(false);
}
}
template <typename T>
ReflectionByteCode _GetReflected(const openvariant<T>*)
{
@if (CEDA_ENABLE_STRING_TABLES)
{
static octet_t bc[] = { FT_OPEN_VARIANT, FT_INTERFACE, 0x00, 0x00 };
return ReflectionByteCode(bc,&GetReflectedInterface<T>().name);
}
@else
{
static_assert(false);
}
}
} // namespace ceda