Archive.h
// Archive.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2004-2019
#pragma once
#ifndef Ceda_cxUtils_Archive_H
#define Ceda_cxUtils_Archive_H
#include "cxUtils.h"
#include "Detect.h"
#include "VectorOfByte.h"
#include "VariableLengthSerialise.h"
#include "MemoryAccess.h"
#include <cstring>
namespace ceda
{
struct IOutputStream;
struct IInputStream;
///////////////////////////////////////////////////////////////////////////////////////////////////
// Platform independent serialisation of basic types
// assuming little-endian order on the wire
inline void Serialise(octet_t* p, bool v) { *p = (octet_t)v; }
inline void Serialise(octet_t* p, uint8 v) { *p = v; }
inline void Serialise(octet_t* p, uint16 v) { SetUnalignedLE(p,v); }
inline void Serialise(octet_t* p, uint32 v) { SetUnalignedLE(p,v); }
inline void Serialise(octet_t* p, uint64 v) { SetUnalignedLE(p,v); }
inline void Serialise(octet_t* p, int8 v) { *p = v; }
inline void Serialise(octet_t* p, int16 v) { SetUnalignedLE(p,v); }
inline void Serialise(octet_t* p, int32 v) { SetUnalignedLE(p,v); }
inline void Serialise(octet_t* p, int64 v) { SetUnalignedLE(p,v); }
inline void Serialise(octet_t* p, float32 v) { SetUnalignedLE(p,v); }
inline void Serialise(octet_t* p, float64 v) { SetUnalignedLE(p,v); }
///////////////////////////////////////////////////////////////////////////////////////////////////
// Platform independent deserialisation of basic types
// assuming little-endian order on the wire
inline void Deserialise(const octet_t* p, bool& v) { v = (*p != 0); }
inline void Deserialise(const octet_t* p, uint8& v) { v = *p; }
inline void Deserialise(const octet_t* p, uint16& v) { GetUnalignedLE(v,p); }
inline void Deserialise(const octet_t* p, uint32& v) { GetUnalignedLE(v,p); }
inline void Deserialise(const octet_t* p, uint64& v) { GetUnalignedLE(v,p); }
inline void Deserialise(const octet_t* p, int8& v) { v = *p; }
inline void Deserialise(const octet_t* p, int16& v) { GetUnalignedLE(v,p); }
inline void Deserialise(const octet_t* p, int32& v) { GetUnalignedLE(v,p); }
inline void Deserialise(const octet_t* p, int64& v) { GetUnalignedLE(v,p); }
inline void Deserialise(const octet_t* p, float32& v) { GetUnalignedLE(v,p); }
inline void Deserialise(const octet_t* p, float64& v) { GetUnalignedLE(v,p); }
///////////////////////////////////////////////////////////////////////////////////////////////////
// InputArchive
/*
An InputArchive can only be used to read from a contiguous block of memory. That means an
InputArchive just wraps a const octet_t*. The deserialisation code always knows when it's
finished based on what was read, so there's no need for an InputArchive to record the size of the
buffer.
This approach eliminates the need for buffer underflow checks - which would otherwise be needed
on every single read operation. This is a significant benefit for the performance.
Since an InputArchive is not associated with some kind of input stream, deserialisation is
"pure CPU". It avoids the anti-pattern of fine grained interleaving of CPU and I/O.
In some cases it may be possible to directly read from a buffer in the underlying input
device (since the const octet_t* pointer can be made to point where we like). For example,
we might be able to read directly from a segment in the LSS segment cache - since most
objects are much smaller than the LSS segment size (e.g. 4MB). This reduces memory consumption,
avoids memory allocations, avoids memcpy's and improves CPU memory cache utilisation.
Verification by the ceda framework
----------------------------------
A possible concern is the lack of buffer underflow checks - having these checks (at least in
a debug build) can be very useful to track down errors in code.
The ceda framework will normally verify that the InputArchive hadn't read past EOF after
deserialisation was completed so there's no chance that such errors go undetected.
In fact the ceda framework will normally ensure that application defined deserialise code
reads the message, the whole message and nothing but the message. Therefore simply flexing
the code unit tests it quite well.
*/
class InputArchive
{
public:
// Allow implicit conversions to/from a const octet_t*
InputArchive(const octet_t* p) : p_(p) {}
inline operator const octet_t*() const { return p_; }
inline const octet_t* ptr() const { return p_; }
inline void SetPtr(const octet_t* p) { p_ = p; }
inline InputArchive& operator+=(ssize_t n) { p_ += n; return *this; }
inline InputArchive& operator-=(ssize_t n) { p_ -= n; return *this; }
inline void ReadBuffer(void* buffer, ssize_t numBytes)
{
std::memcpy(buffer,p_,numBytes);
p_ += numBytes;
}
template <typename T>
inline void Deserialise1248(T& t)
{
GetUnalignedLE(t,p_);
p_ += sizeof(T);
}
// Deserialise a POD data type which is recorded verbatum in the archive
template <typename T>
inline void DeserialisePod(T& t)
{
GetUnaligned(t,p_);
p_ += sizeof(T);
}
// Deserialise a POD data type which is recorded verbatum in the archive
template <typename T>
inline void DeserialiseT(T& t)
{
Deserialise(p_, t);
p_ += sizeof(T);
}
private:
const octet_t* p_;
};
template<typename T>
inline InputArchive& operator>>(InputArchive& ar, T& x)
{
Deserialise(ar,x);
return ar;
}
inline void Deserialise(InputArchive& ar, bool& x) { ar.DeserialiseT(x); }
inline void Deserialise(InputArchive& ar, char& x) { ar.Deserialise1248(x); }
inline void Deserialise(InputArchive& ar, signed char& x) { ar.Deserialise1248(x); }
inline void Deserialise(InputArchive& ar, unsigned char& x) { ar.Deserialise1248(x); }
#ifdef __cpp_char8_t
inline void Deserialise(InputArchive& ar, char8_t& x) { ar.Deserialise1248(x); }
#endif
inline void Deserialise(InputArchive& ar, char16_t& x) { ar.Deserialise1248(x); }
inline void Deserialise(InputArchive& ar, char32_t& x) { ar.Deserialise1248(x); }
inline void Deserialise(InputArchive& ar, wchar_t& x) { ar.Deserialise1248(x); }
inline void Deserialise(InputArchive& ar, short& x) { ar.Deserialise1248(x); }
inline void Deserialise(InputArchive& ar, unsigned short& x) { ar.Deserialise1248(x); }
inline void Deserialise(InputArchive& ar, int& x) { ar.Deserialise1248(x); }
inline void Deserialise(InputArchive& ar, unsigned int& x) { ar.Deserialise1248(x); }
inline void Deserialise(InputArchive& ar, long& x) { ar.Deserialise1248(x); }
inline void Deserialise(InputArchive& ar, unsigned long& x) { ar.Deserialise1248(x); }
inline void Deserialise(InputArchive& ar, long long& x) { ar.Deserialise1248(x); }
inline void Deserialise(InputArchive& ar, unsigned long long& x) { ar.Deserialise1248(x); }
inline void Deserialise(InputArchive& ar, float& x) { ar.DeserialiseT(x); }
inline void Deserialise(InputArchive& ar, double& x) { ar.DeserialiseT(x); }
inline void Deserialise(InputArchive& ar, long double& x) { ar.Deserialise1248(x); }
template<typename T, size_t N>
inline void Deserialise(InputArchive& ar,T (&x)[N])
{
for (size_t i=0 ; i < N ; ++i)
ar >> x[i];
}
// Allow instances of a class having a Deserialise member function to be deserialised
template <typename T> using Deserialise_method_t = decltype(std::declval<T&>().Deserialise(std::declval<InputArchive&>()));
template <typename T> using has_Deserialise_method = is_detected<Deserialise_method_t, T>;
template <typename T, std::enable_if_t<has_Deserialise_method<T>::value, int> = 0>
inline void Deserialise(InputArchive& ar, T& x)
{
x.Deserialise(ar);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// OutputArchive
/*
OutputArchive allows for extremely high performance serialisation of objects to a buffer in
memory, regarded as a sequence of octets.
An OutputArchive is essentially just a octet_t* pointer to the next write position in memory. It is
assumed the memory buffer is large enough, there are no buffer overflow checks or a need for buffer
reallocations during serialisation.
Typically the buffer is allocated to be large enough in the first place by using SizeArchive to
measure the buffer size required before the OutputArchive is used.
For every type T that supports serialisation, the following function is implemented
void Serialise(OutputArchive& ar, const T& x)
Therefore there can be many overloads of Serialise (i.e. adhoc polymorphism).
For convenience clients can use operator<< to serialise variables/objects. E.g.
ar << x << y << z;
is shorthand for
Serialise(ar,x);
Serialise(ar,y);
Serialise(ar,z);
*/
class OutputArchive
{
public:
// Allow implicit conversions to/from a octet_t*
OutputArchive(octet_t* p) : p_(p) {}
inline operator octet_t*() const { return p_; }
inline octet_t* ptr() const { return p_; }
inline void SetPtr(octet_t* p) { p_ = p; }
inline OutputArchive& operator+=(ssize_t n) { p_ += n; return *this; }
inline OutputArchive& operator-=(ssize_t n) { p_ -= n; return *this; }
// Used for padding with zeros
void FillWithZeros(ssize_t numBytes)
{
std::memset(p_, 0, numBytes);
p_ += numBytes;
}
template <typename T>
inline void Serialise1248(const T& t)
{
SetUnalignedLE(p_,t);
p_ += sizeof(T);
}
// Serialise a POD data type which is recorded verbatum in the archive
template <typename T>
inline void SerialisePod(const T& t)
{
SetUnaligned(p_,t);
p_ += sizeof(T);
}
inline void WriteBuffer(const void* buffer, ssize_t numBytes)
{
std::memcpy(p_,buffer,numBytes);
p_ += numBytes;
}
private:
octet_t* p_;
};
template<typename T>
inline OutputArchive& operator<<(OutputArchive& ar, const T& x)
{
Serialise(ar,x);
return ar;
}
inline void Serialise(OutputArchive& ar, bool x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, char x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, signed char x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, unsigned char x) { ar.Serialise1248(x); }
#ifdef __cpp_char8_t
inline void Serialise(OutputArchive& ar, char8_t x) { ar.Serialise1248(x); }
#endif
inline void Serialise(OutputArchive& ar, char16_t x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, char32_t x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, wchar_t x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, short x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, unsigned short x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, int x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, unsigned int x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, long x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, unsigned long x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, long long x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, unsigned long long x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, float x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, double x) { ar.Serialise1248(x); }
inline void Serialise(OutputArchive& ar, long double x) { ar.Serialise1248(x); }
template<typename T, size_t N>
inline void Serialise(OutputArchive& ar, const T (&x)[N])
{
for (size_t i=0 ; i < N ; ++i)
ar << x[i];
}
template <typename T> using Serialise_out_method_t = decltype(std::declval<const T&>().Serialise(std::declval<OutputArchive&>()));
template <typename T> using has_Serialise_out_method = is_detected<Serialise_out_method_t, T>;
template <typename T, std::enable_if_t<has_Serialise_out_method<T>::value, int> = 0>
inline void Serialise(OutputArchive& ar, const T& x)
{
x.Serialise(ar);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/*
SizeArchive is used to calculate the number of octets whch will be written to an OutputArchive.
This allows for pre-allocating the buffer.
SizeArchive& operator<<(SizeArchive& ar, const T& x);
SizeArchive is a thin wrapper on a ssize_t which accumulates the archive size.
This will inline quite well
E.g.
size += 2;
size += 4;
for (int i=0 ; i < n ; ++i)
{
size += 10;
}
size += 2;
reduces to
size += 8 + 10*n;
*/
class SizeArchive
{
public:
// Allow implicit conversions to/from a ssize_t
SizeArchive(ssize_t size) : size_(size) {}
inline operator ssize_t() const { return size_; }
inline SizeArchive& operator+=(ssize_t n) { size_ += n; return *this; }
inline SizeArchive& operator-=(ssize_t n) { size_ -= n; return *this; }
// Serialise a POD data type which is recorded verbatum in the archive
template <typename T>
inline void SerialisePod(const T& t)
{
size_ += sizeof(T);
}
inline void WriteBuffer(const void* buffer, ssize_t numBytes)
{
size_ += numBytes;
}
private:
ssize_t size_;
};
template<typename T>
inline SizeArchive& operator<<(SizeArchive& ar, const T& x)
{
Serialise(ar,x);
return ar;
}
template<typename T>
inline ssize_t GetSerialisedSize(const T& x)
{
SizeArchive ar(0);
ar << x;
return ar;
}
inline void Serialise(SizeArchive& ar, bool x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, char x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, signed char x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, unsigned char x) { ar.SerialisePod(x); }
#ifdef __cpp_char8_t
inline void Serialise(SizeArchive& ar, char8_t x) { ar.SerialisePod(x); }
#endif
inline void Serialise(SizeArchive& ar, char16_t x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, char32_t x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, wchar_t x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, short x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, unsigned short x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, int x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, unsigned int x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, long x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, unsigned long x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, long long x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, unsigned long long x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, float x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, double x) { ar.SerialisePod(x); }
inline void Serialise(SizeArchive& ar, long double x) { ar.SerialisePod(x); }
template <typename T> using Serialise_size_method_t = decltype(std::declval<const T&>().Serialise(std::declval<SizeArchive&>()));
template <typename T> using has_Serialise_size_method = is_detected<Serialise_size_method_t, T>;
template <typename T, std::enable_if_t<has_Serialise_size_method<T>::value, int> = 0>
inline void Serialise(SizeArchive& ar, const T& x)
{
x.Serialise(ar);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// The code below this point is the "old" slower means for serialising and deserialising objects
// Hack to allow fiddling of oid highs when they are serialised
struct IItemSerialiser
{
virtual void SerialiseItem(Archive& ar, const void* pItem) = 0;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
// Archive
// A concrete class for writing data to a binary output stream using insertion operators
class cxUtils_API Archive
{
cxNotCloneable(Archive)
public:
explicit Archive(IOutputStream* oStream);
~Archive();
// Warning: this can defeat information recorded about the number of bytes written
// to the stream.
IOutputStream* GetOutputStream()
{
FlushBuffer();
return m_oStream;
}
octet_t* BeginWrite(ssize_t reserveSize)
{
cxAssert(m_writeBuffer <= m_pos && m_pos <= m_writeBuffer+ARCHIVE_BUFSIZE);
cxAssert(reserveSize <= ARCHIVE_BUFSIZE);
if (m_pos-m_writeBuffer > ARCHIVE_BUFSIZE - reserveSize) FlushBuffer();
return m_pos;
}
void EndWrite(octet_t* pos)
{
cxAssert(m_writeBuffer <= m_pos && m_pos <= pos && pos <= m_writeBuffer+ARCHIVE_BUFSIZE);
m_pos = pos;
}
template <typename T>
void Serialise1248(const T& t)
{
cxAssert(m_writeBuffer <= m_pos && m_pos <= m_writeBuffer+ARCHIVE_BUFSIZE);
cxAssert(sizeof(t) <= ARCHIVE_BUFSIZE);
if (m_pos > m_writeBuffer + (ARCHIVE_BUFSIZE - sizeof(T))) FlushBuffer();
SetUnalignedLE(m_pos,t);
m_pos += sizeof(T);
}
template <typename T>
void SerialisePod(const T& t)
{
cxAssert(m_writeBuffer <= m_pos && m_pos <= m_writeBuffer+ARCHIVE_BUFSIZE);
cxAssert(sizeof(t) <= ARCHIVE_BUFSIZE);
if (m_pos > m_writeBuffer + (ARCHIVE_BUFSIZE - sizeof(T))) FlushBuffer();
SetUnaligned(m_pos,t);
m_pos += sizeof(T);
}
void FillWithZeros(ssize_t numBytes);
// Write the given buffer to the archive
void WriteBuffer(const void* buffer, ssize_t numBytes);
// Returns number of bytes transferred
ssize_t WriteStream(IInputStream* src);
// Copy the given number of bytes from the src to the archive. Returns the actual number of bytes
// copied (if met EOF on the src)
ssize_t WriteStream(IInputStream* src, ssize_t numBytes);
ssize_t NumBufferedWrites() const { return m_pos - m_writeBuffer; }
void ClearWriteBuffer() { m_pos = m_writeBuffer; }
void ClearWriteBufferAndNumBytesWritten()
{
m_pos = m_writeBuffer;
m_numBytesWrittenToStream = 0;
}
void Flush();
// Provides the number of bytes that have been written to the underlying stream using
// the archive, which may be less than the number of bytes written to the archive due
// to write buffering.
int64 GetNumBytesWrittenToStream() const { return m_numBytesWrittenToStream; }
// Get the number of bytes that have been written to this Archive.
int64 GetNumBytesWritten() const { return m_numBytesWrittenToStream + m_pos - m_writeBuffer; }
protected:
void FlushBuffer();
private:
enum { ARCHIVE_BUFSIZE = 4096 };
octet_t m_writeBuffer[ARCHIVE_BUFSIZE];
octet_t* m_pos;
IOutputStream* m_oStream;
int64 m_numBytesWrittenToStream;
public:
IItemSerialiser* m_oidHighSerialiser;
};
template<typename T>
inline Archive& operator<<(Archive& ar, const T& x)
{
Serialise(ar,x);
return ar;
}
// Unfortunately allowing for serialising a bool means we can serialise types with an
// implicit conversion to bool, such as a T* or a ptr<T>
inline void Serialise(Archive& ar, bool x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, char x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, signed char x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, unsigned char x) { ar.Serialise1248(x); }
#ifdef __cpp_char8_t
inline void Serialise(Archive& ar, char8_t x) { ar.Serialise1248(x); }
#endif
inline void Serialise(Archive& ar, char16_t x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, char32_t x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, wchar_t x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, short x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, unsigned short x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, int x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, unsigned int x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, long x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, unsigned long x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, long long x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, unsigned long long x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, float x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, double x) { ar.Serialise1248(x); }
inline void Serialise(Archive& ar, long double x) { ar.Serialise1248(x); }
template <typename T> using Serialise_method_t = decltype(std::declval<const T&>().Serialise(std::declval<Archive&>()));
template <typename T> using has_Serialise_method = is_detected<Serialise_method_t, T>;
template <typename T, std::enable_if_t<has_Serialise_method<T>::value, int> = 0>
inline void Serialise(Archive& ar, const T& x)
{
x.Serialise(ar);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Serialisation of variable length integers
template<typename T>
inline void DeserialiseVariableLengthUint(InputArchive& ar, T& v)
{
ar.SetPtr(DeserialiseVariableLengthInteger(ar.ptr(),v));
}
template<typename T>
inline void DeserialiseVariableLengthUint(InputArchive& ar, T& v, bool& escape)
{
ar.SetPtr(DeserialiseVariableLengthInteger(ar.ptr(),v,escape));
}
template<typename T>
inline void SerialiseVariableLengthUint(Archive& ar, T v)
{
octet_t* p = ar.BeginWrite(MAX_VARIABLE_LENGTH_UINT_SIZE);
p = SerialiseVariableLengthInteger(p, v);
ar.EndWrite(p);
}
template<typename T>
inline void SerialiseVariableLengthUint(OutputArchive& ar, T v)
{
ar.SetPtr(SerialiseVariableLengthInteger(ar.ptr(),v));
}
template<typename T>
inline void SerialiseVariableLengthUint(SizeArchive& ar, T v)
{
ar += GetSizeVariableLengthInteger(v);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// CompressedInt<T>
class Archive;
template<typename T>
struct CompressedInt
{
explicit CompressedInt(T v = 0) : val(v) {}
bool operator==(const CompressedInt& rhs) const { return val == rhs.val; }
bool operator!=(const CompressedInt& rhs) const { return val != rhs.val; }
bool operator<(const CompressedInt& rhs) const { return val < rhs.val; }
T val;
};
template<typename T>
CompressedInt<T>& CastAsCompressedInt(T& v)
{
return reinterpret_cast< CompressedInt<T>& >(v);
}
template<typename T>
CompressedInt<T> AsCompressedInt(T v)
{
return CompressedInt<T>(v);
}
template<typename T>
xostream& operator<<(xostream& os, CompressedInt<T> x)
{
os << x.val;
return os;
}
template<typename Archive, typename T>
inline void Serialise(Archive& ar, CompressedInt<T> x)
{
SerialiseVariableLengthUint(ar, x.val);
}
template<typename Archive, typename T>
inline void Deserialise(Archive& ar, CompressedInt<T>& x)
{
DeserialiseVariableLengthUint(ar, x.val);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Support for schema numbers in the serialised data.
typedef int schema_t;
template <typename Archive>
inline void SerialiseSchema(Archive& ar, schema_t schema)
{
cxAssert(schema > 0);
ar << AsCompressedInt(schema);
}
cxUtils_API void DeserialiseSchema(InputArchive& ar, schema_t maxSchema, schema_t& schema);
///////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T> using Deserialise_t = decltype(Deserialise(std::declval<InputArchive&>(), std::declval<T&>()));
template <typename T> using has_Deserialise = is_detected<Deserialise_t, T>;
// Serialise(Archive&, bool) is defined therefore pointers can be serialised! This is because there's an implicit
// conversion from a pointer to bool
// For that reason, it's not a good idea to detect existence of Serialise to determine whether it's reflected.
//template <typename T> using Serialise_t = decltype(Serialise(std::declval<Archive&>(), std::declval<T const &>()));
//template <typename T> using has_Serialise = is_detected<Serialise_t, T>;
template <typename T> using IsSerialisable = is_detected<Deserialise_t, T>;
} // namespace ceda
#endif // include guard