Commit b345b8cf authored by EXT Arnaud Clère's avatar EXT Arnaud Clère

Partially renamed as in Qt proposal

parent b59d09c6
# Design
The core QBind implementation (excluding IBind implementations) is a few hundreds line of C++11 using templates defined
in the headers, an abstract IBind class, and IWriter/IReader base classes.
The core QBind implementation (excluding QAbstractValue implementations) is a few hundreds line of C++11 using templates defined
in the headers, an abstract QAbstractValue class, and QAbstractValueWriter/QAbstractValueReader base classes.
## The key idea
......@@ -16,7 +16,7 @@ another generic dataset, binding the related parts together. In effect:
Hence, from now on, we will use the term *bind* instead of the more restricted *(de)serialization* term.
This traversal is driven by QBind<T> methods which may use a BindMode (Read,Write,...) to determine whether to read the generic dataset or write it according to the C++ one.
This traversal is driven by QBind<T> methods which may use a QValueMode (Read,Write,...) to determine whether to read the generic dataset or write it according to the C++ one.
[^1]: *traverse* meaning to go through without returning back
......@@ -46,42 +46,42 @@ ways of representing data structures such as XML (e.g. binding the Person type w
The QBind traversal is formally described by the following recursive automaton:
```mermaid
graph LR
subgraph Val
subgraph QVal
i((start))--"null()" --> x((end))
i((start))--"bind#lt;T>()"--> x((end))
i((start))--"sequence()" --> Seq
i((start))--"record()" --> Rec
Seq --"out()" --> x((end))
Rec --"out()" --> x((end))
Seq --"item()" --> vs["Val#lt;Seq>"]
Rec --"item(name)" --> vr["Val#lt;Rec>"]
i((start))--"sequence()" --> QSeq
i((start))--"record()" --> QRec
QSeq --"out()" --> x((end))
QRec --"out()" --> x((end))
QSeq --"item()" --> vs["QVal#lt;QSeq>"]
QRec --"item(name)" --> vr["QVal#lt;QRec>"]
end
```
- Boxes (nested) represent possible states when traversing the data, the automaton is always in a single valid state
- Edges represent possible state transitions that translate to specific data format read/write actions
The automaton is implemented as follows:
- `Val<_>`, `Rec<_>` and `Seq<_>` types implement possible states where _ denotes the type of outer state
- `QVal<_>`, `QRec<_>` and `QSeq<_>` types implement possible states where _ denotes the type of outer state
- State types only expose public methods corresponding to possible transitions, that return the destination state type
- The initial state type is `Val<Cursor>` and the final state is `Cursor` (for instance: `Val<Cursor>::null()` returns a `Cursor`)
- Returning a `Rec<_>` or `Seq<_>` type automatically convert to the final `Cursor` type invoking as much `out()` transitions as required
- `Cursor` is a non-owning pointer to an `IBind` interface which implementations translate data traversal into specific
- The initial state type is `QValue` and the final state is `QValueStatus` (for instance: `QValue::null()` returns a `QValueStatus`)
- Returning a `QRec<_>` or `QSeq<_>` type automatically convert to the final `QValueStatus` type invoking as much `out()` transitions as required
- `QValueStatus` is a non-owning pointer to an `QAbstractValue` interface which implementations translate data traversal into specific
data format read/write actions
- `Cursor` instance is moved from the start state type to the end state type only for successful transitions, allowing to test
- `QValueStatus` instance is moved from the start state type to the end state type only for successful transitions, allowing to test
alternatives before proceeding with the traversal
- Transitions may fail for various reasons specific to `IBind` implementations:
- Transitions may fail for various reasons specific to `QAbstractValue` implementations:
- builders may not be able to allocate new items
- readers may read data not matching the expected transition
- ...
- In case of unsuccessfull transition the returned state type receives a null `Cursor` that transparently bypasses calls to `IBind`
- `bind<T>()` calls are forwarded to the actual `IBind` or generic `QBind` depending on `BindSupport<T>`:
- BindNative : **IBind** interface method
- In case of unsuccessfull transition the returned state type receives a null `QValueStatus` that transparently bypasses calls to `QAbstractValue`
- `bind<T>()` calls are forwarded to the actual `QAbstractValue` or generic `QBind` depending on `BindSupport<T>`:
- BindNative : **QAbstractValue** interface method
- BindGeneric : **QBind** template specialization for T
- Every `bind<T>()` starts from a Val<Cursor> which is an un *unsafe* Cursor copy wrt well-formedness (these `unsafeItem()` copies are protected from incorrect use)
- Every `bind<T>()` starts from a QValue which is an un *unsafe* QValueStatus copy wrt well-formedness (these `unsafeItem()` copies are protected from incorrect use)
## C++ types extensibility
QBind is a functor templated on T type receiving a Value and T reference (either lvalue or rvalue reference) and returning the Cursor.
QBind is a functor templated on T type receiving a Value and T reference (either lvalue or rvalue reference) and returning the QValueStatus.
Template specializations can be defined for any T and optionally refined for specific Cur<TImpl> with different sets of BindNative types.
A default QBind specialization attempts to call `T::bind(...)` to conveniently bind `T* this` without having to understand template syntax,
......@@ -95,35 +95,35 @@ editor will propose to either `bind(myData.item)`, or to construct a `sequence()
## Well-formedness guarantees
Thanks to this design, the compiler will make sure that the only possibility to return a Cursor from a `Val<Cursor>` is to traverse
the data without backtracking, calling only and all necessary IBind virtual methods.
Thanks to this design, the compiler will make sure that the only possibility to return a QValueStatus from a `QValue` is to traverse
the data without backtracking, calling only and all necessary QAbstractValue virtual methods.
The addition of default and optional values take into account most data schema evolutions in a purely declarative fluent interface without
having to test schema versions and the like. The benefit is that it is not possible to introduce bugs using just the fluent interface.
The downside is that writing loops with the fluent interface is unnatural as one must never forget to follow the valid Cursor.
The downside is that writing loops with the fluent interface is unnatural as one must never forget to follow the valid QValueStatus.
For instance:
```cpp
auto seq(v.sequence());
for (auto&& t : ts) {
seq = seq.bind(t); // do not forget to reassign seq, or subsequent items will be `bind` to the moved-from Cursor and automatically ignored
seq = seq.bind(t); // do not forget to reassign seq, or subsequent items will be `bind` to the moved-from QValueStatus and automatically ignored
}
```
## Write performance
Since `Cursor`, `Val`, `Rec` and `Seq` have no data member other than outer types and `IBind*`, calling their methods can be
Since `QValueStatus`, `QVal`, `QRec` and `QSeq` have no data member other than outer types and `QAbstractValue*`, calling their methods can be
optimized and replaced with just the following operations:
1. test the IBind pointer validity [^1]
2. call the IBind virtual method corresponding to the possible transitions
3. return the resulting Cursor, Val, Rec or Seq with a valid or invalid Cursor depending on IBind method success or failure
1. test the QAbstractValue pointer validity [^1]
2. call the QAbstractValue virtual method corresponding to the possible transitions
3. return the resulting QValueStatus, QVal, QRec or QSeq with a valid or invalid QValueStatus depending on QAbstractValue method success or failure
[^1]: Experiments to use constexpr to bypass this step for writers that always return true did not seem to improve performance.
`QBind<T>` can define up to 3 bind() overloads to efficiently and conveniently handle lvalue references, const lvalue references, and
rvalue references depending on T characteristics (which of copy/move is 1/possible and 2/efficient).
Compared to manually calling non-virtual, format-specific implementations, the overhead of always testing the validity of IBind*
Compared to manually calling non-virtual, format-specific implementations, the overhead of always testing the validity of QAbstractValue*
and calling virtual methods is around 20% in our benchmark, with a maximum of 100% for trivial format-specific implementations
like copying a single char to a pre-allocated buffer.
......@@ -135,31 +135,31 @@ Other than that, write performance depends on several factors:
distinguishable from QByteArray binary data
- Using QData<TContent> classes to tag string encodings allowed to pinpoint unnecessary encoding conversions, notably in QVariant handling
- In the end, directly using QByteArray buffers instead of using QIODevice can amount to ~ 2x better write performance
- IBind implementations need to use efficient data structures from storing state. For instance, using an optimized std::vector<bool>
- QAbstractValue implementations need to use efficient data structures from storing state. For instance, using an optimized std::vector<bool>
to memorize opened JSON object/array can usually be stored in a single byte and avoid memory allocations, resulting in ~ 10x better
write performance compared to QVector<bool>
## Read robustness
Performance is not so important for Read. But compared to manually calling non-virtual, format-specific implementations, QBind
enforces well-formedness checks necessary to reliably read data coming from unknown sources (IBind implementations being responsible
enforces well-formedness checks necessary to reliably read data coming from unknown sources (QAbstractValue implementations being responsible
for low-level checks).
All errors are reported as `QIdentifierLiteral` to the `IBind` implementations that will decide what to do with them:
- ignore the details and set a global error status enumeration is usually appropriate to IWriter implementations
- storing all read mismatches is usually more appropriate to world-facing IReader implementations
All errors are reported as `QIdentifierLiteral` to the `QAbstractValue` implementations that will decide what to do with them:
- ignore the details and set a global error status enumeration is usually appropriate to QAbstractValueWriter implementations
- storing all read mismatches is usually more appropriate to world-facing QAbstractValueReader implementations
Standardizing error literals allows efficient reporting and analysis while ensuring that various libraries can define new ones independantly.
## Data types extensibility
IBind is an abstract base class for translating fluent interface calls to format-specific read or write actions.
The fluent interface guarantees that IBind virtual methods are always called at appropriate times, so IBind implementations do not
QAbstractValue is an abstract base class for translating fluent interface calls to format-specific read or write actions.
The fluent interface guarantees that QAbstractValue virtual methods are always called at appropriate times, so QAbstractValue implementations do not
have to check well-formedness. It defines a set of BindNative types and default textual representations and encodings for non-textual
native types simplifying again the implementations (see TextWriter example).
IWriter and IReader provide partial IBind implementations simplifying the work of implementors and offering default textual representations.
QAbstractValueWriter and QAbstractValueReader provide partial QAbstractValue implementations simplifying the work of implementors and offering default textual representations.
*NB:* BindNative types could be extended by specifying BindSupport<T> trait but then, (de)serialization code must be specialized
for each TImpl. For instance, a QBind<QColor,QDataStream> may be implemented differently from QBind<QColor,IBind> but QBind<QColor>
for each TImpl. For instance, a QBind<QColor,QDataStream> may be implemented differently from QBind<QColor,QAbstractValue> but QBind<QColor>
example shows that meta() can also be used to avoid specialized serialization code that breaks RW2 requirement. If meta() is deemed
enough, the BindSupport trait and TImpl template parameters can be removed.
......@@ -25,4 +25,4 @@ QIdentifierLiteral qmChildren ("children" );
QIdentifierLiteral qmName ("name" );
QIdentifierLiteral qmColor ("color" );
Val<Cursor> Cursor::value() noexcept { return Val<Cursor>(std::move(*this)); }
QValue QValueStatus::value() noexcept { return QValue(std::move(*this)); }
......@@ -84,10 +84,10 @@ extern QIdentifierLiteral qBindIgnoredBytes ;
// meta(QIdentifierLiteral name, QAsciiData value) is optional meta-data about current data to enable optimized processing (e.g. tag, , style, etc.) and/or transmission (QAbstractItemModel headers, CBOR tags, XML attribute, CSS, numpy.ndarray shapes, etc.)
extern QIdentifierLiteral qmDataStreamVersion; //!< Allows IBind support of QDataStream
extern QIdentifierLiteral qmDataStreamVersion; //!< Allows QAbstractValue support of QDataStream
// N-dimensional data structures for which specific IWriter may have optimized implementations
// BEWARE though that nested calls to IWriter are not guaranteed to follow the declared structure (this would prevent reusing general QBind functors for nested data structures)
// N-dimensional data structures for which specific QAbstractValueWriter may have optimized implementations
// BEWARE though that nested calls to QAbstractValueWriter are not guaranteed to follow the declared structure (this would prevent reusing general QBind functors for nested data structures)
//! Sequence of records can be implemented as table with columns below (record item names are always the same in a fixed order)
//! Contains comma-separated names
......@@ -109,12 +109,12 @@ extern QIdentifierLiteral qmColor;
// //////////////////////////////////////////////////////////////////////////
// QBind<T>
enum BindMode { Invalid=0, Read=1, Write=2 }; //!< Specifies QBind::bind traversal and processing (the design would support other BindMode like Append or Diff)
enum QValueMode { Invalid=0, Read=1, Write=2 }; //!< Specifies QBind::bind traversal and processing (the design would support other QValueMode like Append or Diff)
struct BindGeneric {};
struct BindNative {};
//!< Specifies whether Val calls IBind::_bind or QBind<T>::bind
//!< \remark It would be possible to remove BindSupport by defining all IBind overloads in Val
//!< Specifies whether QVal calls QAbstractValue::tryBind or QBind<T>::bind
//!< \remark It would be possible to remove BindSupport by defining all QAbstractValue overloads in QVal
template<typename T, typename TEnabledIf=void> struct BindSupport : BindGeneric {};
template<> struct BindSupport< QUtf8Data> : BindNative {};
......@@ -144,82 +144,82 @@ template<> struct BindSupport< QVariant> : BindNative {};
//#include <QtCore/qdatetime.h>
//#include <QtCore/quuid.h>
//! Interface for Cursor with a fixed subset of BindNative types and just a few optional methods
struct IBind {
virtual ~IBind() = default;
//! Interface for QValueStatus with a fixed subset of BindNative types and just a few optional methods
struct QAbstractValue {
virtual ~QAbstractValue() = default;
virtual BindMode mode() const = 0; //!< \remark a static constexpr BindMode Mode did not exhibit noticeable performance improvements and may trigger twice more code generation for Read/Write independant QBind like Person::bind
virtual QValueMode mode() const = 0; //!< \remark a static constexpr QValueMode Mode did not exhibit noticeable performance improvements and may trigger twice more code generation for Read/Write independant QBind like Person::bind
virtual bool _isOk() = 0; //!< Current operation status
virtual bool _sequence(quint32* size=nullptr) = 0;
virtual bool _record (quint32* size=nullptr) = 0;
virtual bool _null ( ) = 0;
virtual bool _item( ) = 0;
virtual bool _item(QIdentifier& n) = 0;
virtual bool _item(QIdentifierLiteral n) = 0;
virtual bool _out ( ) = 0; //!< End of sequence or record
virtual bool _bind( QUtf8DataView u) = 0;
virtual bool _bind( const char* u) = 0;
virtual bool _bind( QAsciiDataView a) = 0;
virtual bool _bind( QLatin1String l) = 0;
virtual bool _bind( QStringView s) = 0;
virtual bool _bind( QUtf8Data& r) = 0;
virtual bool _bind( QString& r) = 0;
virtual bool _bind( bool& r) = 0;
virtual bool _bind( qint8& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool _bind( quint8& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool _bind( qint16& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool _bind( quint16& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool _bind( qint32& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool _bind( quint32& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool _bind( qint64& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool _bind( quint64& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool _bind( float& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool _bind( double& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool _bind( QByteArray& r) = 0;
virtual bool _bind( QVariant& r) = 0;
virtual bool trySequence(quint32* size=nullptr) = 0;
virtual bool tryRecord (quint32* size=nullptr) = 0;
virtual bool tryNull ( ) = 0;
virtual bool tryItem( ) = 0;
virtual bool tryItem(QIdentifier& n) = 0;
virtual bool tryItem(QIdentifierLiteral n) = 0;
virtual bool tryOut ( ) = 0; //!< End of sequence or record
virtual bool tryBind( QUtf8DataView u) = 0;
virtual bool tryBind( const char* u) = 0;
virtual bool tryBind( QAsciiDataView a) = 0;
virtual bool tryBind( QLatin1String l) = 0;
virtual bool tryBind( QStringView s) = 0;
virtual bool tryBind( QUtf8Data& r) = 0;
virtual bool tryBind( QString& r) = 0;
virtual bool tryBind( bool& r) = 0;
virtual bool tryBind( qint8& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool tryBind( quint8& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool tryBind( qint16& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool tryBind( quint16& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool tryBind( qint32& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool tryBind( quint32& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool tryBind( qint64& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool tryBind( quint64& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool tryBind( float& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool tryBind( double& r) = 0; //!< \warning Must return false instead of losing sign or digit
virtual bool tryBind( QByteArray& r) = 0;
virtual bool tryBind( QVariant& r) = 0;
// TODO QChar, QDateTime, QDate, QTime, QUuid (text or numerical)
// Overloads for const& and && T
virtual bool _bind(const QUtf8Data& r) = 0;
virtual bool _bind(const QString& r) = 0;
virtual bool _bind(const bool& r) = 0;
virtual bool _bind(const qint8& r) = 0;
virtual bool _bind(const quint8& r) = 0;
virtual bool _bind(const qint16& r) = 0;
virtual bool _bind(const quint16& r) = 0;
virtual bool _bind(const qint32& r) = 0;
virtual bool _bind(const quint32& r) = 0;
virtual bool _bind(const qint64& r) = 0;
virtual bool _bind(const quint64& r) = 0;
virtual bool _bind(const float& r) = 0;
virtual bool _bind(const double& r) = 0;
virtual bool _bind(const QByteArray& r) = 0;
virtual bool _bind(const QVariant& r) = 0;
virtual bool _bind( QUtf8Data&& r) = 0;
virtual bool _bind( QString&& r) = 0;
virtual bool _bind( bool&& r) = 0;
virtual bool _bind( qint8&& r) = 0;
virtual bool _bind( quint8&& r) = 0;
virtual bool _bind( qint16&& r) = 0;
virtual bool _bind( quint16&& r) = 0;
virtual bool _bind( qint32&& r) = 0;
virtual bool _bind( quint32&& r) = 0;
virtual bool _bind( qint64&& r) = 0;
virtual bool _bind( quint64&& r) = 0;
virtual bool _bind( float&& r) = 0;
virtual bool _bind( double&& r) = 0;
virtual bool _bind( QByteArray&& r) = 0;
virtual bool _bind( QVariant&& r) = 0;
virtual bool _any() { return _null(); }
virtual bool tryBind(const QUtf8Data& r) = 0;
virtual bool tryBind(const QString& r) = 0;
virtual bool tryBind(const bool& r) = 0;
virtual bool tryBind(const qint8& r) = 0;
virtual bool tryBind(const quint8& r) = 0;
virtual bool tryBind(const qint16& r) = 0;
virtual bool tryBind(const quint16& r) = 0;
virtual bool tryBind(const qint32& r) = 0;
virtual bool tryBind(const quint32& r) = 0;
virtual bool tryBind(const qint64& r) = 0;
virtual bool tryBind(const quint64& r) = 0;
virtual bool tryBind(const float& r) = 0;
virtual bool tryBind(const double& r) = 0;
virtual bool tryBind(const QByteArray& r) = 0;
virtual bool tryBind(const QVariant& r) = 0;
virtual bool tryBind( QUtf8Data&& r) = 0;
virtual bool tryBind( QString&& r) = 0;
virtual bool tryBind( bool&& r) = 0;
virtual bool tryBind( qint8&& r) = 0;
virtual bool tryBind( quint8&& r) = 0;
virtual bool tryBind( qint16&& r) = 0;
virtual bool tryBind( quint16&& r) = 0;
virtual bool tryBind( qint32&& r) = 0;
virtual bool tryBind( quint32&& r) = 0;
virtual bool tryBind( qint64&& r) = 0;
virtual bool tryBind( quint64&& r) = 0;
virtual bool tryBind( float&& r) = 0;
virtual bool tryBind( double&& r) = 0;
virtual bool tryBind( QByteArray&& r) = 0;
virtual bool tryBind( QVariant&& r) = 0;
virtual bool _any() { return tryNull(); }
//! \warning meta() is ignored by default and subject to various interpretation, so users should define or adopt existing meta data standards like XSD for sake of interoperability
virtual void _meta(QIdentifierLiteral&, QAsciiData&) {}
......@@ -231,69 +231,73 @@ struct IBind {
// //////////////////////////////////////////////////////////////////////////
// Default TResult implementations
template<class T_> class Val;
template<class T_> class QVal;
#include <type_traits>
template<class T> using RemoveCvRef = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
//! A Cursor is a move-only, non-owning pointer to IBind that can be used as QBind's result
//! It allows to easily add QBind support to existing Reader/Writers by adding at least one value() method returning a Cursor(this).value()
//! A QValueStatus is a move-only, non-owning pointer to QAbstractValue
//!
//! Its public interface only allows checking the status of resulting QAbstractValue operation(s) using operator bool() and reportError().
//! Its protected interface is used by QVal, QSeq, QRec to only allow a new operation QAbstractValue operation from the previous valid QValueStatus.
//!
//! It allows to easily add QBind support to existing Reader/Writers by adding at least one value() method returning a QValueStatus(this).value()
//! \see TextWriter in main.cpp
//!
//! \remark Cursor allows using the fluent interface without checking intermediate status by setting impl=nullptr on error
//! \remark QValueStatus allows using the fluent interface without checking intermediate status by setting impl=nullptr on error
//! and subsequently bypassing impl calls. Errors can result from:
//! - using the same intermediate Val/Seq/Rec twice (a programmer error), or
//! - runtime impl errors like trying to bind a _sequence() whereas the data read matches a _record()
//! - using the same intermediate QVal/QSeq/QRec twice (a programmer error), or
//! - runtime impl errors like trying to bind a trySequence() whereas the data read matches a tryRecord()
//!
class Cursor
class QValueStatus
{
Q_DISABLE_COPY(Cursor)
Q_DISABLE_COPY(QValueStatus)
public:
Q_ENABLE_MOVE_DEFAULT(Cursor)
Q_ENABLE_MOVE_DEFAULT(QValueStatus)
explicit Cursor(IBind* i) : impl(i) { Q_ASSERT(impl); }
explicit QValueStatus(QAbstractValue* i) : impl(i) { Q_ASSERT(impl); }
BindMode mode() const noexcept { return impl ? impl->mode() : BindMode::Invalid; }
QValueMode mode() const noexcept { return impl ? impl->mode() : QValueMode::Invalid; }
operator bool() const noexcept { return impl && impl->_isOk(); } //!< Drives QBind<T>::bind() traversal
Cursor* operator ->() noexcept { return this; }
Val<Cursor> value() noexcept ;
QValueStatus* operator ->() noexcept { return this; }
QVal<QValueStatus> value() noexcept ;
void setChoice (bool c) { if (impl) impl->_setChoice (c); }
void reportError(QIdentifierLiteral e) { if (impl) impl->_reportError(e); }
void reportError(const char* e) { reportError(QIdentifierLiteral(e)); }
protected:
template<class T_> friend class Val; // enables calling methods below
template<class T_> friend class Seq;
template<class T_> friend class Rec;
template<class T_> friend class QVal; // enables calling methods below
template<class T_> friend class QSeq;
template<class T_> friend class QRec;
void _meta (QIdentifierLiteral& n,
QAsciiData& m) { if (Q_LIKELY(impl) ) impl->_meta (n,m); } //!< idempotent (can be called by more than one code)
bool _sequence( quint32* s=nullptr) { return Q_LIKELY(impl) && impl->_sequence(s); }
bool _record ( quint32* s=nullptr) { return Q_LIKELY(impl) && impl->_record (s); }
bool _null ( ) { return Q_LIKELY(impl) && impl->_null ( ); }
bool trySequence( quint32* s=nullptr) { return Q_LIKELY(impl) && impl->trySequence(s); }
bool tryRecord ( quint32* s=nullptr) { return Q_LIKELY(impl) && impl->tryRecord (s); }
bool tryNull ( ) { return Q_LIKELY(impl) && impl->tryNull ( ); }
bool _any ( ) { return Q_LIKELY(impl) && impl->_any ( ); }
bool _bind ( QUtf8DataView u) { return Q_LIKELY(impl) && impl->_bind (u); }
bool _bind ( QAsciiDataView a) { return Q_LIKELY(impl) && impl->_bind (a); }
bool _bind ( QLatin1String l) { return Q_LIKELY(impl) && impl->_bind (l); }
bool _bind ( QStringView u) { return Q_LIKELY(impl) && impl->_bind (u); }
bool tryBind ( QUtf8DataView u) { return Q_LIKELY(impl) && impl->tryBind (u); }
bool tryBind ( QAsciiDataView a) { return Q_LIKELY(impl) && impl->tryBind (a); }
bool tryBind ( QLatin1String l) { return Q_LIKELY(impl) && impl->tryBind (l); }
bool tryBind ( QStringView u) { return Q_LIKELY(impl) && impl->tryBind (u); }
template<typename T> bool _bind( T&& t) { return _bind(BindSupport<RemoveCvRef<T>>(), std::forward<T>(t)); }
template<typename T> bool tryBind( T&& t) { return tryBind(BindSupport<RemoveCvRef<T>>(), std::forward<T>(t)); }
private:
template<typename T> bool _bind(BindNative , T&& t) { return Q_LIKELY(impl) && impl->_bind( std::forward<T>(t)); }
template<typename T> bool _bind(BindGeneric, T&& t) ;
template<typename T> bool tryBind(BindNative , T&& t) { return Q_LIKELY(impl) && impl->tryBind( std::forward<T>(t)); }
template<typename T> bool tryBind(BindGeneric, T&& t) ;
bool _item(QIdentifierLiteral n) { return Q_LIKELY(impl) && impl->_item(n); }
bool _item(QIdentifier& n) { return Q_LIKELY(impl) && impl->_item(n); }
bool _item( ) { return Q_LIKELY(impl) && impl->_item( ); }
bool _out ( ) { return Q_LIKELY(impl) && impl->_out ( ); }
bool tryItem(QIdentifierLiteral n) { return Q_LIKELY(impl) && impl->tryItem(n); }
bool tryItem(QIdentifier& n) { return Q_LIKELY(impl) && impl->tryItem(n); }
bool tryItem( ) { return Q_LIKELY(impl) && impl->tryItem( ); }
bool tryOut ( ) { return Q_LIKELY(impl) && impl->tryOut ( ); }
private:
Cursor _unsafeCopy() noexcept { return impl ? Cursor(impl) : Cursor(); }
QValueStatus _unsafeCopy() noexcept { return impl ? QValueStatus(impl) : QValueStatus(); }
IBind* impl = nullptr;
QAbstractValue* impl = nullptr;
};
//!< A pair of T value reference and defaultValue to use instead of null()
template<typename T> struct QBindDefault { T& value; const T& defaultValue; };
template<typename T> struct QDefaultValue { T& value; const T& defaultValue; };
// //////////////////////////////////////////////////////////////////////////
// Generic fluent interface for traversing structured values and processing them along the way:
......@@ -301,189 +305,193 @@ template<typename T> struct QBindDefault { T& value; const T& defaultValue; };
// - constructing generic in-memory data structures
// - etc...
template<class T_> class Rec; //!< a Record data structure defined below
template<class T_> class Seq; //!< a Sequence data structure defined below
template<class T_> class Val; //!< a choice of sequence(), record(), null(), or any value with at least a textual representation and possibly binary ones
template<class T_> class QRec; //!< a Record data structure defined below
template<class T_> class QSeq; //!< a Sequence data structure defined below
template<class T_> class QVal; //!< a choice of sequence(), record(), null(), or any value with at least a textual representation and possibly binary ones
using QValue = QVal<QValueStatus>;
using QSequence = QSeq<QValueStatus>;
using QRecord = QRec<QValueStatus>;
// Custom bind support
#include <functional>
#ifndef NO_COMPILER_RTTI_OR_EXCEPTIONS
template<typename T> using QBindFunction = Cursor (*)(T &,Val<Cursor>&&) ;
template<class Ts> using QBindSeqFunction = Seq<Cursor>(*)(Ts&,Seq<Cursor>&&) ;
template<typename T> using QValFunction = QValueStatus (*)(T &,QValue &&) ;
template<class Ts> using QSeqFunction = QSequence (*)(Ts&,QSequence&&) ;
#endif
/**/ using QBindLambda = std::function< Cursor ( Val<Cursor>&&)>;
/**/ using QBindSeqLambda = std::function<Seq<Cursor> ( Seq<Cursor>&&)>;
/**/ using QValLambda = std::function<QValueStatus(QValue &&)>;
/**/ using QSeqLambda = std::function<QSequence (QSequence&&)>;
template<class T_> class Val
template<class T_> class QVal
{
Q_DISABLE_COPY(Val)
Q_DISABLE_COPY(QVal)
public:
Q_ENABLE_MOVE_DEFAULT(Val)
explicit Val(T_&& out) noexcept { std::swap(outer, out); }
Q_ENABLE_MOVE_DEFAULT(QVal)
explicit QVal(T_&& out) noexcept { std::swap(outer, out); }
operator bool() const noexcept { return outer.operator bool(); } //!< Drives QBind<T>::bind() traversal
Cursor* operator->() noexcept { return outer.operator ->(); }
QValueStatus* operator->() noexcept { return outer.operator ->(); }
Val<T_> meta (QIdentifierLiteral& n, QAsciiData& m) { outer->_meta(n,m); return std::move(*this); }
QVal<T_> meta (QIdentifierLiteral& n, QAsciiData& m) { outer->_meta(n,m); return std::move(*this); }
Seq<T_> sequence(quint32* s=nullptr) { return outer->_sequence (s) ? Seq<T_>(std::move(outer)) : Seq<T_>(); }
Rec<T_> record (quint32* s=nullptr) { return outer->_record (s) ? Rec<T_>(std::move(outer)) : Rec<T_>(); }
/**/T_ null ( ) { return outer->_null ( ) ? std::move(outer) : T_ (); }
QSeq<T_> sequence(quint32* s=nullptr) { return outer->trySequence (s) ? QSeq<T_>(std::move(outer)) : QSeq<T_>(); }
QRec<T_> record (quint32* s=nullptr) { return outer->tryRecord (s) ? QRec<T_>(std::move(outer)) : QRec<T_>(); }
/**/T_ null ( ) { return outer->tryNull ( ) ? std::move(outer) : T_ (); }
/**/T_ any ( ) { return outer->_any ( ) ? std::move(outer) : T_ (); }
/**/T_ bind ( const char* u) { return outer->_bind(QUtf8DataView(u)) ? std::move(outer) : T_ (); }
/**/T_ bind ( QUtf8DataView u) { return outer->_bind (u) ? std::move(outer) : T_ (); }
/**/T_ bind ( QAsciiDataView a) { return outer->_bind (a) ? std::move(outer) : T_ (); }
/**/T_ bind ( QLatin1String l) { return outer->_bind (l) ? std::move(outer) : T_ (); }
/**/T_ bind ( QStringView u) { return outer->_bind (u) ? std::move(outer) : T_ (); }
/**/T_ bind ( const char* u) { return outer->tryBind(QUtf8DataView(u)) ? std::move(outer) : T_ (); }
/**/T_ bind ( QUtf8DataView u) { return outer->tryBind (u) ? std::move(outer) : T_ (); }
/**/T_ bind ( QAsciiDataView a) { return outer->tryBind (a) ? std::move(outer) : T_ (); }
/**/T_ bind ( QLatin1String l) { return outer->tryBind (l) ? std::move(outer) : T_ (); }
/**/T_ bind ( QStringView u) { return outer->tryBind (u) ? std::move(outer) : T_ (); }
template<typename T> T_ bind ( T&& t) { return outer->_bind(std::forward<T>(t )) ? std::move(outer) : T_(); }
template<typename T> T_ bind (T& t, T&& defaultT) { return outer->_bind(QBindDefault<T>{t,defaultT}) ? std::move(outer) : T_(); }
template<typename T> T_ bind ( T&& t) { return outer->tryBind(std::forward<T>(t )) ? std::move(outer) : T_(); }
template<typename T> T_ bind (T& t, T&& defaultT) { return outer->tryBind(QDefaultValue<T>{t,defaultT}) ? std::move(outer) : T_(); }
// Custom bind support
/**/ T_ with ( QBindLambda customBind) { return customBind( std::move(unsafeThis())) ? std::move(outer) : T_(); }
template<typename T> T_ with (T& t, QBindFunction<T> customBind) { return customBind(t, std::move(unsafeThis())) ? std::move(outer) : T_(); }
/**/ T_ with ( QValLambda customBind) { return customBind( std::move(unsafeThis())) ? std::move(outer) : T_(); }
template<typename T> T_ with (T& t, QValFunction<T> customBind) { return customBind(t, std::move(unsafeThis())) ? std::move(outer) : T_(); }
// Literal metadata support
Val<T_> meta(const char* n, const char* m) { return meta(QIdentifierLiteral(n),QAsciiData(m)); }
Val<T_> meta( QIdentifierLiteral&& n, QAsciiData&& m) { QIdentifierLiteral nref=n; QAsciiData mref= m ; return meta(nref, mref); }
Val<T_> meta(const QIdentifierLiteral& n, QAsciiData&& m) { QIdentifierLiteral nref=n; QAsciiData mref= m ; return meta(nref, mref); }
Val<T_> meta(const QIdentifierLiteral& n, const char* m) { QIdentifierLiteral nref=n; QAsciiData mref=QAsciiData(m); return meta(nref, mref); }
Val<T_> meta( QIdentifierLiteral&& n, const QAsciiData& m) { QIdentifierLiteral nref=n; QAsciiData mref= m ; return meta(nref, mref); }
QVal<T_> meta(const char* n, const char* m) { return meta(QIdentifierLiteral(n),QAsciiData(m)); }
QVal<T_> meta( QIdentifierLiteral&& n, QAsciiData&& m) { QIdentifierLiteral nref=n; QAsciiData mref= m ; return meta(nref, mref); }
QVal<T_> meta(const QIdentifierLiteral& n, QAsciiData&& m) { QIdentifierLiteral nref=n; QAsciiData mref= m ; return meta(nref, mref); }
QVal<T_> meta(const QIdentifierLiteral& n, const char* m) { QIdentifierLiteral nref=n; QAsciiData mref=QAsciiData(m); return meta(nref, mref); }
QVal<T_> meta( QIdentifierLiteral&& n, const QAsciiData& m) { QIdentifierLiteral nref=n; QAsciiData mref= m ; return meta(nref, mref); }
Seq<T_> sequence(quint32 s) { return sequence(&s); }
Rec<T_> record (quint32 s) { return record (&s); }
Rec<T_> record (const char* n) { return meta(qmName,QAsciiData(n)).record(); }
QSeq<T_> sequence(quint32 s) { return sequence(&s); }
QRec<T_> record (quint32 s) { return record (&s); }