TypeOps.h

// TypeOps.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2012

@import "cxObject.h"
@import "Array.h"
#include "Ceda/cxUtils/Archive.h"
#include "Ceda/cxUtils/CedaAssert.h"
#include "Ceda/cxUtils/Detect.h"
#include "Ceda/cxUtils/Bounds.h"

namespace ceda
{
struct ReflectionByteCode;
struct ReflectedType;
enum class ECompareResult;

struct IObjectVisitor;
template <typename T> using VisitObjects_t = decltype(VisitObjects(std::declval<IObjectVisitor&>(), std::declval<T const&>()));
template <typename T> using has_VisitObjects = is_detected<VisitObjects_t, T>;

struct IPrefVisitor;
template <typename T> using VisitPrefs_t = decltype(VisitPrefs(std::declval<IPrefVisitor&>(), std::declval<T const&>()));
template <typename T> using has_VisitPrefs = is_detected<VisitPrefs_t, T>;

template <typename T> using has_AssignToReflectedVariableFn_t = decltype( CoerceToRV( std::declval<const ReflectedType&>(), nullptr, std::declval<const T&>(), false) );
template <typename T> using has_AssignToReflectedVariableFn = is_detected<has_AssignToReflectedVariableFn_t, T>;

template <typename T> using has_AssignFromReflectedVariableFn_t = decltype( CoerceFromRV(std::declval<T&>(), std::declval<const ReflectedType&>(), nullptr, false) );
template <typename T> using has_AssignFromReflectedVariableFn = is_detected<has_AssignFromReflectedVariableFn_t, T>;

// Check for existance of ECompareResult RVCompare(const T& lhs, const ReflectedType& rhs_type, const void* rhs, bool checkReverse);
template <typename T> using has_RVCompare_t = decltype( RVCompare(std::declval<const T&>(), std::declval<const ReflectedType&>(), nullptr, false) );
template <typename T> using has_RVCompare = is_detected<has_RVCompare_t, T>;

///////////////////////////////////////////////////////////////////////////////////////////////////
// Function signatures

////////////// Variable

typedef void (__stdcall *ConstructVariableFn)(void* p);
typedef void (__stdcall *DestructVariableFn)(void* p);
typedef void (__stdcall *SerialiseVariableFn)(Archive& ar, const void* p);
typedef void (__stdcall *DeserialiseVariableFn)(InputArchive& ar, void* p);
typedef void (__stdcall *VisitIObjectsFn)(IObjectVisitor& v, const void* p);
typedef void (__stdcall *VisitPrefsFn)(IPrefVisitor& v, const void* p);

typedef void (__stdcall *CopyConstructVariableFn)(void* p1, const void* p2);
typedef void (__stdcall *AssignVariableFn)(void* p1, const void* p2);
typedef bool (__stdcall *CompareVariableFn)(const void* p1, const void* p2);
typedef void (__stdcall *PrintVariableFn)(xostream& os, const void* p);

/*
Contruct/Assign lhs so it holds the value recorded in rhs.
Returns false if lhs cannot hold the value of rhs.

If 'allowExplicitConversions' is true then coercions are enabled.

If allowExplicitConversions is false then the function succeeds if and only if the set of
values of the type of lhs contains the current value of rhs.  For example if lhs is of type int8 and the
rhs is an int32 with the value 100 then the function succeeds.  The way a value in rhs happens to be
encoded should be irrelevant.

The type of one of the variables 'self' is implicit, as when these functions pointers
appear in a TypeOps.  The type of the other variable is defined by a given ReflectionByteCode
*/

/////// deprecated
typedef bool (__stdcall *ConstructVariableFromSelfFn)(
    ReflectionByteCode lhs_rbc, const void* lhs,
    const void* rhs,    // self
    bool allowExplicitConversions);

typedef bool (__stdcall *ConstructSelfFromVariableFn)(
    void* lhs,          // self
    ReflectionByteCode rhs_rbc, const void* rhs,
    bool allowExplicitConversions);

typedef bool (__stdcall *AssignVariableFromSelfFn)(
    ReflectionByteCode lhs_rbc, const void* lhs,
    const void* rhs,    // self
    bool allowExplicitConversions);

typedef bool (__stdcall *AssignSelfFromVariableFn)(
    void* lhs,          // self
    ReflectionByteCode rhs_rbc, const void* rhs,
    bool allowExplicitConversions);
/////// end deprecated

typedef bool (__stdcall *AssignToReflectedVariableFn)(
    const ReflectedType& lhs_type, void* lhs,
    const void* rhs,    // self
    bool allowExplicitConversions);

typedef bool (__stdcall *AssignFromReflectedVariableFn)(
    void* lhs,          // self
    const ReflectedType& rhs_type, const void* rhs,
    bool allowExplicitConversions);

typedef ECompareResult (__stdcall *RVCompareFn)(const void* lhs, const ReflectedType& rhs_type, const void* rhs, bool checkReverse);

////////////// Array

/*
The following functions are typically parameterised in

    void* A                 ptr to array
    ssize_t s               stride to be added to A to advance to the next element of the array
    ssize_t n               number of elements in the array
*/

typedef void (__stdcall *ConstructArrayFn)( void* A, ssize_t s, ssize_t n);
typedef void (__stdcall *DestructArrayFn)( void* A, ssize_t s, ssize_t n);
typedef void (__stdcall *SerialiseArrayFn)(Archive& ar, const void* A, ssize_t s, ssize_t n);
typedef void (__stdcall *DeserialiseArrayFn)(InputArchive& ar, void* A, ssize_t s, ssize_t n);
typedef void (__stdcall *VisitObjectsInArrayFn)(IObjectVisitor& v, const void* A, ssize_t s, ssize_t n);
typedef void (__stdcall *VisitPrefsInArrayFn)(IPrefVisitor& v, const void* A, ssize_t s, ssize_t n);
typedef ssize_t (__stdcall *FindInArrayFn)(const void* A, ssize_t s, ssize_t n, const void* v);
typedef void (__stdcall *CopyConstructArrayFn)(void* A1, ssize_t s1, const void* A2, ssize_t s2, ssize_t n);
typedef void (__stdcall *AssignArrayFn)(void* A1, ssize_t s1, const void* A2, ssize_t s2, ssize_t n);

///////////////////////////////////////////////////////////////////////////////////////////////////

// Used to generate an implementation of a function of type VisitPrefsFn that is appropriate for
// a variable of type T.  This is used to fill in the 'visitPrefsFn' member of FieldParams.
template <typename T>
void __stdcall MakeVisitPrefsFunction(IPrefVisitor& v, const void* p)
{
    v << * (T*) p;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// TypeOps

struct @api TypeOps
{
    uint32 GetTypeOperationFlags() const;

    inline ssize_t GetSize() const 
    { 
        return size; 
    }

    inline ssize_t GetMemoryAlignment() const 
    { 
        return memoryAlignment; 
    }

    inline bool HasDefaultConstructor() const
    {
        return constructArrayFn != nullptr;
    }
    inline void ConstructArray(void* A, ssize_t s, ssize_t n) const
    {
        cxAssert(0 <= s);
        cxAssert(0 <= n);
        cxAssert(n == 0 || (A != nullptr && s > 0));
        cxAssert(constructArrayFn);
        
        constructArrayFn(A,s,n);
    }
    inline void ConstructArray(void* A, ssize_t n) const
    {
        cxAssert(0 <= n);
        cxAssert(n == 0 || A != nullptr);
        cxAssert(constructArrayFn);
        
        constructArrayFn(A,size,n);
    }
    inline void ConstructVariable(void* p) const
    {
        cxAssert(p);
        cxAssert(constructArrayFn);
        
        constructArrayFn(p,size,1);
    }
    inline bool HasDestructor() const
    {
        return destructArrayFn != nullptr;
    }
    inline void DestructArray(void* A, ssize_t s, ssize_t n) const
    {
        cxAssert(0 <= s);
        cxAssert(0 <= n);
        cxAssert(n == 0 || (A != nullptr && s > 0));
        
        if (destructArrayFn) destructArrayFn(A,s,n);
    }
    inline void DestructArray(void* A, ssize_t n) const
    {
        cxAssert(0 <= n);
        cxAssert(n == 0 || A != nullptr);
        
        if (destructArrayFn) destructArrayFn(A,size,n);
    }
    inline void DestructVariable(void* p) const
    {
        cxAssert(p);
        
        if (destructArrayFn) destructArrayFn(p,0,1);
    }
    inline bool HasSerialise() const
    {
        return serialiseArrayFn != nullptr;
    }
    inline void SerialiseArray(Archive& ar, const void* A, ssize_t s, ssize_t n) const
    {
        cxAssert(0 <= s);
        cxAssert(0 <= n);
        cxAssert(n == 0 || (A != nullptr && s > 0));
        cxAssert(serialiseArrayFn);
        
        serialiseArrayFn(ar,A,s,n);
    }
    inline void SerialiseArray(Archive& ar, const void* A, ssize_t n) const
    {
        cxAssert(0 <= n);
        cxAssert(n == 0 || A != nullptr);
        cxAssert(serialiseArrayFn);
        
        serialiseArrayFn(ar,A,size,n);
    }
    inline void SerialiseVariable(Archive& ar, const void* p) const
    {
        cxAssert(p);
        cxAssert(serialiseArrayFn);
        
        serialiseArrayFn(ar,p,0,1);
    }
    inline bool HasDeserialise() const
    {
        return deserialiseArrayFn != nullptr;
    }
    inline void DeserialiseArray(InputArchive& ar, void* A, ssize_t s, ssize_t n) const
    {
        cxAssert(0 <= s);
        cxAssert(0 <= n);
        cxAssert(n == 0 || (A != nullptr && s > 0));
        cxAssert(deserialiseArrayFn);
        
        deserialiseArrayFn(ar,A,s,n);
    }
    inline void DeserialiseArray(InputArchive& ar, void* A, ssize_t n) const
    {
        cxAssert(0 <= n);
        cxAssert(n == 0 || A != nullptr);
        cxAssert(deserialiseArrayFn);
        
        deserialiseArrayFn(ar,A,size,n);
    }
    inline void DeserialiseVariable(InputArchive& ar, void* p) const
    {
        cxAssert(p);
        cxAssert(deserialiseArrayFn);
        
        deserialiseArrayFn(ar,p,0,1);
    }

    inline bool HasScanOver() const
    {
        return HasDefaultConstructor() && HasDeserialise();
    }
    inline void ScanOverVariable(InputArchive& ar) const
    {
        ScanOverArray(ar,1);
    }
    inline void ScanOverArray(InputArchive& ar, ssize_t n) const
    {
        cxAssert(0 <= n);
        cxAssert(HasScanOver());

        // todo: inefficient
        xvector<octet_t> buffer(n*size);
        ConstructArray(buffer.data(), size, n);
        DeserialiseArray(ar, buffer.data(), size, n);
        DestructArray(buffer.data(), size, n);
    }

    inline bool HasVisitObjects() const
    {
        return visitObjectsInArrayFn != nullptr;
    }
    inline void VisitObjectsInArray(IObjectVisitor& v, const void* A, ssize_t s, ssize_t n) const
    {
        cxAssert(0 <= s);
        cxAssert(0 <= n);
        cxAssert(n == 0 || (A != nullptr && s > 0));
        
        if (visitObjectsInArrayFn) visitObjectsInArrayFn(v,A,s,n);
    }
    inline void VisitObjectsInArray(IObjectVisitor& v, const void* A, ssize_t n) const
    {
        cxAssert(0 <= n);
        cxAssert(n == 0 || A != nullptr);
        
        if (visitObjectsInArrayFn) visitObjectsInArrayFn(v,A,size,n);
    }
    inline void VisitObjectsInVariable(IObjectVisitor& v, const void* p) const
    {
        cxAssert(p);
        
        if (visitObjectsInArrayFn) visitObjectsInArrayFn(v,p,0,1);
    }
    inline bool HasVisitPrefs() const
    {
        return visitPrefsInArrayFn != nullptr;
    }
    inline void VisitPrefsInArray(IPrefVisitor& v, const void* A, ssize_t s, ssize_t n) const
    {
        cxAssert(0 <= s);
        cxAssert(0 <= n);
        cxAssert(n == 0 || (A != nullptr && s > 0));
        
        if (visitPrefsInArrayFn) visitPrefsInArrayFn(v,A,s,n);
    }
    inline void VisitPrefsInArray(IPrefVisitor& v, const void* A, ssize_t n) const
    {
        cxAssert(0 <= n);
        cxAssert(n == 0 || A != nullptr);
        
        if (visitPrefsInArrayFn) visitPrefsInArrayFn(v,A,size,n);
    }
    inline void VisitPrefsInVariable(IPrefVisitor& v, const void* p) const
    {
        cxAssert(p);
        
        if (visitPrefsInArrayFn) visitPrefsInArrayFn(v,p,0,1);
    }
    
    inline bool HasCopyConstruct() const
    {
        return copyConstructArrayFn != nullptr;
    }
    inline void CopyConstructArray(void* A1, ssize_t s1, const void* A2, ssize_t s2, ssize_t n) const
    {
        cxAssert(0 <= n);
        cxAssert(n == 0 || (A1 != nullptr && A2 != nullptr));
        cxAssert(copyConstructArrayFn);
        copyConstructArrayFn(A1,s1,A2,s2,n);
    }
    inline void CopyConstructArray(void* A1, const void* A2, ssize_t n) const
    {
        cxAssert(0 <= n);
        cxAssert(n == 0 || (A1 != nullptr && A2 != nullptr));
        cxAssert(copyConstructArrayFn);
        copyConstructArrayFn(A1,size,A2,size,n);
    }
    inline void CopyConstructVariable(void* p1, const void* p2) const
    {
        cxAssert(p1);
        cxAssert(p2);
        cxAssert(copyConstructArrayFn);
        copyConstructArrayFn(p1,size,p2,size,1);
    }
    
    inline bool HasCopyAssignment() const
    {
        return assignArrayFn != nullptr;
    }
    inline void AssignArray(void* A1, ssize_t s1, const void* A2, ssize_t s2, ssize_t n) const
    {
        cxAssert(0 <= n);
        cxAssert(n == 0 || (A1 != nullptr && A2 != nullptr));
        cxAssert(assignArrayFn);
        assignArrayFn(A1,s1,A2,s2,n);
    }
    inline void AssignArray(void* A1, const void* A2, ssize_t n) const
    {
        cxAssert(0 <= n);
        cxAssert(n == 0 || (A1 != nullptr && A2 != nullptr));
        cxAssert(assignArrayFn);
        assignArrayFn(A1,size,A2,size,n);
    }
    inline void AssignVariable(void* p1, const void* p2) const
    {
        cxAssert(p1);
        cxAssert(p2);
        cxAssert(assignArrayFn);
        assignArrayFn(p1,size,p2,size,1);
    }
    
    /*
    todo: it may be appropriate to support lexicographic compare on arrays.  Maybe a version
    that returns -1, 0 or 1
    */
    inline bool HasCompareLess() const
    {
        return lessFn != nullptr;
    }
    inline bool Less(const void* p1, const void* p2) const
    {
        cxAssert(p1);
        cxAssert(p2);
        cxAssert(lessFn);
        return lessFn(p1,p2);
    }
    inline bool LessOrEqual(const void* p1, const void* p2) const
    {
        cxAssert(p1);
        cxAssert(p2);
        cxAssert(lessFn);
        return !lessFn(p2,p1);
    }
    inline bool Greater(const void* p1, const void* p2) const
    {
        cxAssert(p1);
        cxAssert(p2);
        cxAssert(lessFn);
        return lessFn(p2,p1);
    }
    inline bool GreaterOrEqual(const void* p1, const void* p2) const
    {
        cxAssert(p1);
        cxAssert(p2);
        cxAssert(lessFn);
        return !lessFn(p1,p2);
    }
    inline bool HasCompareEqual() const
    {
        return equalsFn != nullptr;
    }
    inline bool Equal(const void* p1, const void* p2) const
    {
        cxAssert(p1);
        cxAssert(p2);
        cxAssert(equalsFn);
        return equalsFn(p1,p2);
    }
    
    inline bool HasPrint() const
    {
        return printVariableFn != nullptr;
    }
    inline void PrintVariable(xostream& os, const void* p) const
    {
        cxAssert(p);
        cxAssert(printVariableFn);
        printVariableFn(os,p);
    }

    inline bool HasLowerBound() const
    {
        return lowerBoundFn != nullptr;
    }
    inline ssize_t LowerBound(const void* A, ssize_t s, ssize_t n, const void* p) const
    {
        cxAssert(0 <= s);
        cxAssert(0 <= n);
        cxAssert(n == 0 || (A != nullptr && s > 0));
        cxAssert(p);
        cxAssert(lowerBoundFn);
        return lowerBoundFn(A,s,n,p);
    }
    
    inline bool HasUpperBound() const
    {
        return upperBoundFn != nullptr;
    }
    inline ssize_t UpperBound(const void* A, ssize_t s, ssize_t n, const void* p) const
    {
        cxAssert(0 <= s);
        cxAssert(0 <= n);
        cxAssert(n == 0 || (A != nullptr && s > 0));
        cxAssert(p);
        cxAssert(upperBoundFn);
        return upperBoundFn(A,s,n,p);
    }
    
    inline ssize_t ReverseLowerBound(const void* A, ssize_t s, ssize_t n, const void* p) const
    {
        return UpperBound(A,s,n,p)-1;
    }

    inline ssize_t ReverseUpperBound(const void* A, ssize_t s, ssize_t n, const void* p) const
    {
        return LowerBound(A,s,n,p)-1;
    }

    // Returns index 0..n-1 or else n if not found
    inline ssize_t FindFirst(const void* A, ssize_t s, ssize_t n, const void* p) const
    {
        ssize_t i = LowerBound(A, s, n, p);
        return (i != n && Equal(offset_ptr<void>(A, i*s), p)) ? i : n;
    }
        
    // Returns index 0..n-1 or else -1 if not found
    inline ssize_t FindLast(const void* A, ssize_t s, ssize_t n, const void* p) const
    {
        ssize_t i = UpperBound(A, s, n, p) - 1;
        return (i != -1 && Equal(offset_ptr<void>(A, i*s), p)) ? i : -1;
    }

    //////////////////////////// state /////////////////////////////

    const char* name;
    ssize_t size;
    ssize_t memoryAlignment;
    uint64 flags;
    
    CompareVariableFn equalsFn;
    CompareVariableFn lessFn;
    PrintVariableFn printVariableFn;

    ConstructArrayFn constructArrayFn;
    CopyConstructArrayFn copyConstructArrayFn;
    AssignArrayFn assignArrayFn;
    DestructArrayFn destructArrayFn;                // null if trivially destructible
    SerialiseArrayFn serialiseArrayFn;
    DeserialiseArrayFn deserialiseArrayFn;
    VisitObjectsInArrayFn visitObjectsInArrayFn;
    VisitPrefsInArrayFn visitPrefsInArrayFn;
    
    FindInArrayFn lowerBoundFn;
    FindInArrayFn upperBoundFn;

    AssignToReflectedVariableFn assignToReflectedVariableFn;
    AssignFromReflectedVariableFn assignFromReflectedVariableFn;
    RVCompareFn rvCompareFn;

    //ConstructVariableFromSelfFn deprecatedConstructVariableFromSelfFn;
    //ConstructSelfFromVariableFn deprecatedConstructSelfFromVariableFn;
    AssignVariableFromSelfFn deprecatedAssignVariableFromSelfFn;
    AssignSelfFromVariableFn deprecatedAssignSelfFromVariableFn;
};

@api xostream& operator<<(xostream& os, const TypeOps& r);

$extern+ TypeOps;

inline bool IsSameType(const TypeOps& t1, const TypeOps& t2)
{
    return std::strcmp(t1.name, t2.name) == 0;    
}

// Returns the smallest integer x where x >= min and x % align == 0
inline ssize_t NextAlignment(ssize_t min, ssize_t align)
{
    cxAssert(min >= 0);
    cxAssert(align > 0);
    auto k = min % align;
    auto x = (k==0) ? min : (min + align - k);
    cxAssert(x % align == 0);
    cxAssert(min <= x);
    cxAssert(x < min+align);
    return x;
}

inline std::pair<ssize_t,ssize_t> GetLayoutOfPair(const TypeOps& t1, const TypeOps& t2)
{
    auto o = NextAlignment( t1.GetSize(), t2.GetMemoryAlignment() );
    auto a = std::max( t1.GetMemoryAlignment(), t2.GetMemoryAlignment() );
    auto s = NextAlignment( o + t2.GetSize(), a );
    return {o,s};
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// The fundamental operators on variables of type T (T must be a value type), using the 
// special naming conventions for a TypeOps

#ifdef _MSC_VER
    // warning C4345: behavior change: an object of POD type constructed with an initializer of 
    // the form () will be default-initialized
    #pragma warning(disable: 4345)
#endif

template <typename T>
void __stdcall ConstructVariable(T* p)
{
    new(p) T();
}

template <typename T>
void __stdcall DestructVariable(T* p)
{
    p->~T();
}

template <typename T>
void __stdcall CopyConstructVariable(T* p1, const T* p2)
{
    new(p1) T(*p2);
}

template <typename T>
void __stdcall AssignVariable(T* p1, const T* p2)
{
    *p1 = *p2;
}

template <typename T>
void __stdcall SerialiseVariable(Archive& ar, const T* p)
{
    ar << *p;
}

template <typename T>
void __stdcall DeserialiseVariable(InputArchive& ar, T* p)
{
    ar >> *p;
}

template <typename T>
void __stdcall VisitObjectsInVariable(IObjectVisitor& v, const T* p)
{
    v << *p;
}

template <typename T>
void __stdcall VisitPrefsInVariable(IPrefVisitor& v, const T* p)
{
    v << *p;
}

template <typename T>
inline void __stdcall PrintVariable(xostream& os, const T* p)
{
    os << *p;
}

template <typename T>
inline bool __stdcall AssignToReflectedVariable(const ReflectedType& lhs_type, void* lhs, const T* rhs, bool allowExplicitConversions)
{
    return CoerceToRV(lhs_type, lhs, *rhs, allowExplicitConversions);
}

template <typename T>
inline bool __stdcall AssignFromReflectedVariable(T* lhs, const ReflectedType& rhs_type, const void* rhs, bool allowExplicitConversions)
{
    return CoerceFromRV(*lhs, rhs_type, rhs, allowExplicitConversions);
}

template <typename T>
inline ECompareResult __stdcall stub_RVCompare(const T* lhs, const ReflectedType& rhs_type, const void* rhs, bool checkReverse)
{
    return RVCompare(*lhs, rhs_type, rhs, checkReverse);
}

template <typename T>
bool __stdcall VariableLess(const T* p1, const T* p2)
{
    return *p1 < *p2;
}

template <typename T>
bool __stdcall VariableEqual(const T* p1, const T* p2)
{
    return *p1 == *p2;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// The fundamental operators on arrays of variables of type T

template <typename T>
void __stdcall ConstructArray( T* A, ssize_t s, ssize_t n)
{
    if constexpr(std::is_arithmetic<T>::value)
    {
        if (s == sizeof(T))
        {
            std::memset(A, 0, n*sizeof(T));
            return;
        }
    }
    for (ssize_t i = 0 ; i < n ; ++i)
    {
        ConstructVariable(A);
        ApplyStride(A,s);
    }
}
template <typename T>
void __stdcall DestructArray( T* A, ssize_t s, ssize_t n)
{
    if constexpr(!std::is_trivially_destructible<T>::value)
    {
        for (ssize_t i = 0 ; i < n ; ++i)
        {
            DestructVariable(A);
            ApplyStride(A,s);
        }
    }
}
template <typename T>
void __stdcall SerialiseArray(Archive& ar, const T* A, ssize_t s, ssize_t n)
{
    /*
    if constexpr(std::is_arithmetic<T>::value)
    {
        if (s == sizeof(T))
        {
            ar.WriteBuffer(A, n*sizeof(T));
            return;
        }
    }
    */
    for (ssize_t i = 0 ; i < n ; ++i)
    {
        SerialiseVariable(ar,A);
        ApplyStride(A,s);
    }
}
template <typename T>
void __stdcall DeserialiseArray(InputArchive& ar, T* A, ssize_t s, ssize_t n)
{
    /*
    if constexpr(std::is_arithmetic<T>::value)
    {
        if (s == sizeof(T))
        {
            ar.ReadBuffer(A, n*sizeof(T));
            return;
        }
    }
    */

    for (ssize_t i = 0 ; i < n ; ++i)
    {
        DeserialiseVariable(ar,A);
        ApplyStride(A,s);
    }
}
template <typename T>
void __stdcall VisitObjectsInArray(IObjectVisitor& v, const T* A, ssize_t s, ssize_t n)
{
    for (ssize_t i = 0 ; i < n ; ++i)
    {
        VisitObjectsInVariable(v,A);
        ApplyStride(A,s);
    }
}

template <typename T>
void __stdcall VisitPrefsInArray(IPrefVisitor& v, const T* A, ssize_t s, ssize_t n)
{
    for (ssize_t i = 0 ; i < n ; ++i)
    {
        VisitPrefsInVariable(v,A);
        ApplyStride(A,s);
    }
}

template <typename T>
inline void __stdcall CopyConstructArray(T* A1, ssize_t s1, const T* A2, ssize_t s2, ssize_t n)
{
    if constexpr(std::is_trivially_copy_constructible<T>::value)
    {
        if (s1 == sizeof(T) && s2 == sizeof(T))
        {
            std::memcpy(A1, A2, n*sizeof(T));
            return;
        }
    }
    for (ssize_t i = 0 ; i < n ; ++i)
    {
        CopyConstructVariable(A1,A2);
        ApplyStride(A1,s1);
        ApplyStride(A2,s2);
    }
}
template <typename T>
void __stdcall AssignArray(T* A1, ssize_t s1, const T* A2, ssize_t s2, ssize_t n)
{
    if constexpr(std::is_trivially_copy_assignable<T>::value)
    {
        if (s1 == sizeof(T) && s2 == sizeof(T))
        {
            std::memcpy(A1, A2, n*sizeof(T));
            return;
        }
    }
    for (ssize_t i = 0 ; i < n ; ++i)
    {
        AssignVariable(A1,A2);
        ApplyStride(A1,s1);
        ApplyStride(A2,s2);
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////

template <typename T>
ConstructArrayFn GetConstructArrayFn()
{
    if constexpr (std::is_default_constructible<T>::value)
        return (ConstructArrayFn) (void (*)( T*, ssize_t, ssize_t)) ConstructArray<T>;
    else
        return nullptr;
}

template <typename T>
DestructArrayFn GetDestructArrayFn()
{
    static_assert(std::is_destructible<T>::value);
    if constexpr (!std::is_trivially_destructible<T>::value)
        return (DestructArrayFn) (void (*)( T*, ssize_t, ssize_t)) DestructArray<T>;
    else
        return nullptr;
}

template <typename T>
CopyConstructArrayFn GetCopyConstructArrayFn()
{
    if constexpr (std::is_copy_constructible<T>::value)
        return (CopyConstructArrayFn) (void (*)(T*, ssize_t, T const*, ssize_t, ssize_t)) CopyConstructArray<T>;
    else
        return nullptr;
}

template <typename T>
AssignArrayFn GetCopyAssignArrayFn()
{
    if constexpr (std::is_copy_assignable<T>::value)
        return (AssignArrayFn) (void (*)(T*, ssize_t, T const*, ssize_t, ssize_t)) AssignArray<T>;
    else
        return nullptr;
}

template <typename T>
CompareVariableFn GetVariableEqualFn()
{
    if constexpr (has_compare_equal<T,T>::value)
        return (CompareVariableFn) (bool (*)(const T*, const T*)) VariableEqual<T>;
    else
        return nullptr;
}

template <typename T>
CompareVariableFn GetVariableLessFn()
{
    if constexpr (has_compare_less<T,T>::value)
        return (CompareVariableFn) (bool (*)(const T*, const T*)) VariableLess<T>;
    else
        return nullptr;
}

template <typename T>
FindInArrayFn GetBinaryLowerBoundFn()
{
    if constexpr (has_compare_less<T,T>::value)
        return (FindInArrayFn) (ssize_t (*)(T const*, ssize_t, ssize_t, T const*)) BinaryLowerBound<T>;
    else
        return nullptr;
}

template <typename T>
FindInArrayFn GetBinaryUpperBoundFn()
{
    if constexpr (has_compare_less<T,T>::value)
        return (FindInArrayFn) (ssize_t (*)(T const*, ssize_t, ssize_t, T const*)) BinaryUpperBound<T>;
    else
        return nullptr;
}

template <typename T>
SerialiseArrayFn GetSerialiseArrayFn()
{
    if constexpr (IsSerialisable<T>::value)
        return (SerialiseArrayFn) (void (*)(Archive&, T const*, ssize_t, ssize_t)) SerialiseArray<T>;
    else
        return nullptr;
}

template <typename T>
DeserialiseArrayFn GetDeserialiseArrayFn()
{
    if constexpr (IsSerialisable<T>::value)
        return (DeserialiseArrayFn) (void (*)(InputArchive&, T*, ssize_t, ssize_t)) DeserialiseArray<T>;
    else
        return nullptr;
}

template <typename T>
VisitObjectsInArrayFn GetVisitObjectsInArrayFn()
{
    if constexpr (has_VisitObjects<T>::value)
        return (VisitObjectsInArrayFn) (void (*)(IObjectVisitor&, T const*, ssize_t, ssize_t)) VisitObjectsInArray<T>;
    else
        return nullptr;
}

template <typename T>
VisitPrefsInArrayFn GetVisitPrefsInArrayFn()
{
    if constexpr (has_VisitPrefs<T>::value)
        return (VisitPrefsInArrayFn) (void (*)(IPrefVisitor&, T const*, ssize_t, ssize_t)) VisitPrefsInArray<T>;
    else
        return nullptr;
}

template <typename T>
PrintVariableFn GetPrintVariableFn()
{
    if constexpr (has_xostream_insertion<T>::value)
        return (PrintVariableFn) (void (*)(xostream&, T const*)) PrintVariable<T>;
    else
        return nullptr;
}

template <typename T>
AssignToReflectedVariableFn GetAssignToReflectedVariableFn()
{
    if constexpr (has_AssignToReflectedVariableFn<T>::value)
        return (AssignToReflectedVariableFn) (bool (*)(const ReflectedType&, void*, const T*, bool)) AssignToReflectedVariable<T>;
    else
        return nullptr;
}

template <typename T>
AssignFromReflectedVariableFn GetAssignFromReflectedVariableFn()
{
    if constexpr (has_AssignFromReflectedVariableFn<T>::value)
        return (AssignFromReflectedVariableFn) (bool (*)(T*, const ReflectedType&, const void*, bool)) AssignFromReflectedVariable<T>;
    else
        return nullptr;
}

template <typename T>
RVCompareFn GetRVCompareFn()
{
    if constexpr (has_RVCompare<T>::value)
        return (RVCompareFn) (ECompareResult (*)(const T*, const ReflectedType&, const void*, bool)) stub_RVCompare<T>;
    else
        return nullptr;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// TypeInfoFlags

enum class ETypeInfoBits
{
    is_default_constructible,
    is_trivially_default_constructible,
    is_destructible,
    is_trivially_destructible,
    is_copy_constructible,
    is_trivially_copy_constructible,
    is_copy_assignable,
    is_trivially_copy_assignable,

    is_move_constructible,
    is_trivially_move_constructible,
    is_move_assignable,
    is_trivially_move_assignable,
    is_swappable,
    has_virtual_destructor,
    _unused_14,
    _unused_15,

    // Categories
    is_void,
    is_integral,
    is_floating_point,
    is_array,
    is_enum,
    is_union,
    is_class,
    is_function,

    is_pointer,
    is_fundamental,
    is_arithmetic,
    is_scalar,
    is_object,
    is_compound,
    is_reference,
    _unused_31,

    // type properties
    is_const,
    is_volatile,
    is_trivial,
    is_trivially_copyable,
    is_standard_layout,
    has_unique_object_representations,
    is_empty,
    is_polymorphic,

    is_abstract,
    is_final,
    is_aggregate,
    is_signed,
    is_unsigned,
    _unused_45,
    _unused_46,
    _unused_47,

    has_compare_equal,
    has_compare_less,
    IsSerialisable,
    has_VisitObjects,
    has_VisitPrefs,
    has_xostream_insertion,
};

template<typename T>
constexpr uint64 GetTypeInfoFlags()
{
    #define CEDA_CHECK_TYPE_INFO_BIT(name)      if constexpr (std::name<T>::value) v |= ((uint64)1 << (uint64)ETypeInfoBits::name); 
    #define CEDA_CHECK_TYPE_INFO_BIT2(name)     if constexpr (name<T>::value) v |= ((uint64)1 << (uint64)ETypeInfoBits::name); 
    
    uint64 v = 0;
    CEDA_CHECK_TYPE_INFO_BIT(is_default_constructible)
    CEDA_CHECK_TYPE_INFO_BIT(is_trivially_default_constructible)
    CEDA_CHECK_TYPE_INFO_BIT(is_destructible)
    CEDA_CHECK_TYPE_INFO_BIT(is_trivially_destructible)
    CEDA_CHECK_TYPE_INFO_BIT(is_copy_constructible)
    CEDA_CHECK_TYPE_INFO_BIT(is_trivially_copy_constructible)
    CEDA_CHECK_TYPE_INFO_BIT(is_copy_assignable)
    CEDA_CHECK_TYPE_INFO_BIT(is_trivially_copy_assignable)

    CEDA_CHECK_TYPE_INFO_BIT(is_move_constructible)
    CEDA_CHECK_TYPE_INFO_BIT(is_trivially_move_constructible)
    CEDA_CHECK_TYPE_INFO_BIT(is_move_assignable)
    CEDA_CHECK_TYPE_INFO_BIT(is_trivially_move_assignable)
    CEDA_CHECK_TYPE_INFO_BIT(is_swappable)
    CEDA_CHECK_TYPE_INFO_BIT(has_virtual_destructor)

    CEDA_CHECK_TYPE_INFO_BIT(is_void)
    CEDA_CHECK_TYPE_INFO_BIT(is_integral)
    CEDA_CHECK_TYPE_INFO_BIT(is_floating_point)
    CEDA_CHECK_TYPE_INFO_BIT(is_array)
    CEDA_CHECK_TYPE_INFO_BIT(is_enum)
    CEDA_CHECK_TYPE_INFO_BIT(is_union)
    CEDA_CHECK_TYPE_INFO_BIT(is_class)
    CEDA_CHECK_TYPE_INFO_BIT(is_function)
    CEDA_CHECK_TYPE_INFO_BIT(is_pointer)

    CEDA_CHECK_TYPE_INFO_BIT(is_fundamental)
    CEDA_CHECK_TYPE_INFO_BIT(is_arithmetic)
    CEDA_CHECK_TYPE_INFO_BIT(is_scalar)
    CEDA_CHECK_TYPE_INFO_BIT(is_object)
    CEDA_CHECK_TYPE_INFO_BIT(is_compound)
    CEDA_CHECK_TYPE_INFO_BIT(is_reference)

    CEDA_CHECK_TYPE_INFO_BIT(is_const)
    CEDA_CHECK_TYPE_INFO_BIT(is_volatile)
    CEDA_CHECK_TYPE_INFO_BIT(is_trivial)
    CEDA_CHECK_TYPE_INFO_BIT(is_trivially_copyable)
    CEDA_CHECK_TYPE_INFO_BIT(is_standard_layout)
    CEDA_CHECK_TYPE_INFO_BIT(has_unique_object_representations)
    CEDA_CHECK_TYPE_INFO_BIT(is_empty)
    CEDA_CHECK_TYPE_INFO_BIT(is_polymorphic)
    CEDA_CHECK_TYPE_INFO_BIT(is_abstract)
    CEDA_CHECK_TYPE_INFO_BIT(is_final)
    CEDA_CHECK_TYPE_INFO_BIT(is_aggregate)
    CEDA_CHECK_TYPE_INFO_BIT(is_signed)
    CEDA_CHECK_TYPE_INFO_BIT(is_unsigned)

    CEDA_CHECK_TYPE_INFO_BIT2(has_compare_equal)
    CEDA_CHECK_TYPE_INFO_BIT2(has_compare_less)
    CEDA_CHECK_TYPE_INFO_BIT2(IsSerialisable)

    CEDA_CHECK_TYPE_INFO_BIT2(has_VisitObjects)
    CEDA_CHECK_TYPE_INFO_BIT2(has_VisitPrefs)
    CEDA_CHECK_TYPE_INFO_BIT2(has_xostream_insertion)

    return v;
}

template<typename T>
constexpr ssize_t GetMemoryAlignment()
{
    struct P 
    {
        char first;
        T second;
    };

    return offsetof(P,second);
}

///////////////////////////////////////////////////////////////////////////////////////////////////

template <typename T>
const TypeOps& MakeTypeOps()
{
    static_assert(!std::is_abstract_v<T>);

    static TypeOps t =
    {
        typeid(T).name(),
        sizeof(T),
        GetMemoryAlignment<T>(),
        GetTypeInfoFlags<T>(),
        GetVariableEqualFn<T>(),
        GetVariableLessFn<T>(),
        GetPrintVariableFn<T>(),
        GetConstructArrayFn<T>(),
        GetCopyConstructArrayFn<T>(),
        GetCopyAssignArrayFn<T>(),
        GetDestructArrayFn<T>(),
        GetSerialiseArrayFn<T>(),
        GetDeserialiseArrayFn<T>(),
        GetVisitObjectsInArrayFn<T>(),
        GetVisitPrefsInArrayFn<T>(),
        GetBinaryLowerBoundFn<T>(),
        GetBinaryUpperBoundFn<T>(),
        GetAssignToReflectedVariableFn<T>(),
        GetAssignFromReflectedVariableFn<T>(),
        GetRVCompareFn<T>()
    };
    return t;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// GetTypeOps<T>()

template <typename E> class EnumBase;

template <typename T, typename Enable = void>
struct MapTypeOpsType
{
    using type = T;
};

template <typename T, size_t N>
struct MapTypeOpsType<T[N]>
{
    using type = std::array<T,N>;
};

template <typename T>
struct MapTypeOpsType<T, std::enable_if_t<std::is_enum_v<T> || std::is_base_of_v<EnumBase<T>,T>>>
{
    using type = int;
};

class prefbase;
template<typename T> class pref;
template<typename T> class cref;

template <typename T>
struct MapTypeOpsType<pref<T>>
{
    using type = prefbase;
};

template <typename T>
struct MapTypeOpsType<cref<T>>
{
    using type = prefbase;
};

template <typename T>
inline const TypeOps& GetTypeOps()
{
    return MakeTypeOps<typename MapTypeOpsType<T>::type>();
}

template <typename T>
inline const TypeOps* TryGetTypeOps()
{
    if constexpr(std::is_abstract_v<T>)
    {
        return nullptr;
    }
    else
    {
        return &GetTypeOps<T>();
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// TypeOpsRegistry

/*
The TypeOpsRegistry is a singleton registry which is a thread safe map from 
ReflectionByteCodeValue to const TypeOps*.

DLLs are able to insert into this map at start up.  Note that the behaviour of a TypeOps
is uniquely determined by the ReflectionByteCodeValue, so it is ok for a DLL to ignore
a failure to insert an entry because one already exists.
*/

class ReflectionByteCodeValue;

// Use to avoid a link dependency of cxObject.dll on cxPersistStore.dll.  This global variable 
// (exported by cxObject.dll) is initialised by cxPersistStore.dll when cxPersistStore.dll is 
// loaded.
@api extern const TypeOps* g_PrefTypeOps;

/*
Register the given TypeOps, keyed by ReflectionByteCodeValue.
Returns false if a TypeOps for the given ReflectionByteCodeValue has already been registered
*/
@api bool RegisterTypeOps(const ReflectionByteCodeValue& rbcv, const TypeOps* t);

// Find the TypeOps that was registered with the given ReflectionByteCodeValue.
// Returns nullptr if not found.
@api const TypeOps* FindRegisteredTypeOps(const ReflectionByteCodeValue& rbcv);

} // namespace ceda