The core QBind implementation (excluding QAbstractValue implementations) is a few hundreds line of C++11 using templates defined
The core QTransmogrifier 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.
in the headers, an abstract QAbstractValue class, and QAbstractValueWriter/QAbstractValueReader base classes.
## The key idea
## The key idea
QBind is more general than (de)serialization and should be understood as a generic way to traverse[^1] a C++ dataset and
QTransmogrifier is more general than (de)serialization and should be understood as a generic way to traverse[^1] a C++ dataset and
another generic dataset, binding the related parts together. In effect:
another generic dataset, binding the related parts together. In effect:
* the traversal may be partial, leaving out unrelated dataset parts (satisfying R2)
* the traversal may be partial, leaving out unrelated dataset parts (satisfying R2)
* the same traversal may be used to:
* the same traversal may be used to:
...
@@ -16,7 +16,7 @@ another generic dataset, binding the related parts together. In effect:
...
@@ -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.
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 QValueMode (Read,Write,...) to determine whether to read the generic dataset or write it according to the C++ one.
This traversal is driven by QTransmogrifier<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
[^1]:*traverse* meaning to go through without returning back
...
@@ -24,7 +24,7 @@ QDebug and QDataStream translate all data to a "flat" sequence of characters/byt
...
@@ -24,7 +24,7 @@ QDebug and QDataStream translate all data to a "flat" sequence of characters/byt
be determined for sure by reading the code that produced it. But R1, R2, R3 require that we describe our data in a little bit more
be determined for sure by reading the code that produced it. But R1, R2, R3 require that we describe our data in a little bit more
detail. Moreover, W1 and RW2 require that we choose a careful compromise between data format features.
detail. Moreover, W1 and RW2 require that we choose a careful compromise between data format features.
QBind allows binding C++ `data` to a choice of:
QTransmogrifier allows binding C++ `data` to a choice of:
*`sequence` of adjacent `data``item`s
*`sequence` of adjacent `data``item`s
*`record` of named `data``item`s
*`record` of named `data``item`s
*`null` value (meaning no information available on `data`)
*`null` value (meaning no information available on `data`)
...
@@ -34,16 +34,16 @@ QBind allows binding C++ `data` to a choice of:
...
@@ -34,16 +34,16 @@ QBind allows binding C++ `data` to a choice of:
- BindGeneric : **QBind** template specialization for T
- BindGeneric : **QTransmogrifier** template specialization for T
- 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)
- 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
## 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 QValueStatus.
QTransmogrifier 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.
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,
A default QTransmogrifier specialization attempts to call `T::bind(...)` to conveniently bind `T* this` without having to understand template syntax,
rvalue or forwarding references. Such custom implementations are facilitated by the fluent interface below.
rvalue or forwarding references. Such custom implementations are facilitated by the fluent interface below.
## Convenient fluent interface
## Convenient fluent interface
A convenient side-effect of encoding the QBind traversal in the type system is that smart C++ editors offer QBind-aware code completion
A convenient side-effect of encoding the QTransmogrifier traversal in the type system is that smart C++ editors offer QTransmogrifier-aware code completion
to the fluent interface making it similar to using an advanced XML editor for typing XML tags. Say, after typing `Value(myImpl).` the
to the fluent interface making it similar to using an advanced XML editor for typing XML tags. Say, after typing `Value(myImpl).` the
editor will propose to either `bind(myData.item)`, or to construct a `sequence()`, `record()` or `null()` value.
editor will propose to either `bind(myData.item)`, or to construct a `sequence()`, `record()` or `null()` value.
...
@@ -120,7 +120,7 @@ optimized and replaced with just the following operations:
...
@@ -120,7 +120,7 @@ optimized and replaced with just the following operations:
[^1]:Experiments to use constexpr to bypass this step for writers that always return true did not seem to improve performance.
[^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
`QTransmogrifier<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).
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 QAbstractValue*
Compared to manually calling non-virtual, format-specific implementations, the overhead of always testing the validity of QAbstractValue*
...
@@ -141,7 +141,7 @@ Other than that, write performance depends on several factors:
...
@@ -141,7 +141,7 @@ Other than that, write performance depends on several factors:
## Read robustness
## Read robustness
Performance is not so important for Read. But compared to manually calling non-virtual, format-specific implementations, QBind
Performance is not so important for Read. But compared to manually calling non-virtual, format-specific implementations, QTransmogrifier
enforces well-formedness checks necessary to reliably read data coming from unknown sources (QAbstractValue implementations being responsible
enforces well-formedness checks necessary to reliably read data coming from unknown sources (QAbstractValue implementations being responsible
for low-level checks).
for low-level checks).
...
@@ -160,6 +160,6 @@ native types simplifying again the implementations (see TextWriter example).
...
@@ -160,6 +160,6 @@ native types simplifying again the implementations (see TextWriter example).
QAbstractValueWriter and QAbstractValueReader provide partial QAbstractValue 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
*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,QAbstractValue> but QBind<QColor>
for each TImpl. For instance, a QTransmogrifier<QColor,QDataStream> may be implemented differently from QTransmogrifier<QColor,QAbstractValue> but QTransmogrifier<QColor>
example shows that meta() can also be used to avoid specialized serialization code that breaks RW2 requirement. If meta() is deemed
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.
enough, the BindSupport trait and TImpl template parameters can be removed.
classQCborWriter:publicQAbstractValueWriter// TODO Support CBOR tags for QAbstractValue BindNative types and multi-dimensional meta() (e.g. support qmColumns with http://cbor.schmorp.de/stringref, qmSizes with https://datatracker.ietf.org/doc/draft-ietf-cbor-array-tags/?include_text=1 )
classQCborWriter:publicQAbstractValueWriter// TODO Support CBOR tags for QAbstractValue BindNative types and multi-dimensional meta() (e.g. support qmColumns with http://cbor.schmorp.de/stringref, qmSizes with https://datatracker.ietf.org/doc/draft-ietf-cbor-array-tags/?include_text=1 )
//! \warning As with QDataStream, QDataWriter and QDataReader must bind compatible C++ data types, and QDataStream ByteOrder, FloatingPointPrecision and Version
//! \warning As with QDataStream, QDataWriter and QDataReader must bind compatible C++ data types, and QDataStream ByteOrder, FloatingPointPrecision and Version
//! \remark When not statically known, such information can be transmitted using meta("type",...) although some QAbstractValue implementations may not support it
//! \remark When not statically known, such information can be transmitted using meta("type",...) although some QAbstractValue implementations may not support it
classQDataWriter:publicQAbstractValueWriter// allows runtime flexibility but requires QBind<T> in addition to T::operator<<(QDataStream&) and does not warrant QDataStream operators compatibility if QBind<T> ignores qmDataStreamVersion in meta()
classQDataWriter:publicQAbstractValueWriter// allows runtime flexibility but requires QTransmogrifier<T> in addition to T::operator<<(QDataStream&) and does not warrant QDataStream operators compatibility if QTransmogrifier<T> ignores qmDataStreamVersion in meta()
{
{
Q_DISABLE_COPY(QDataWriter)
Q_DISABLE_COPY(QDataWriter)
public:
public:
...
@@ -108,4 +108,4 @@ private:
...
@@ -108,4 +108,4 @@ private:
// \remark Providing a general QDataReader may be deceiving because the QDataStream structure is implicit, thus
// \remark Providing a general QDataReader may be deceiving because the QDataStream structure is implicit, thus
// a reader is usually assumed to statically know the data type to read based on the stream origin and version.
// a reader is usually assumed to statically know the data type to read based on the stream origin and version.
// Though QVariant can be used for parts which type are not statically known though, such as the dynamic type of a message coming from a socket.
// Though QVariant can be used for parts which type are not statically known though, such as the dynamic type of a message coming from a socket.
// Finally, QDataStream version can be retrieved from meta() to adapt to data schema changes over the time \see QBind<QColor> for an example
// Finally, QDataStream version can be retrieved from meta() to adapt to data schema changes over the time \see QTransmogrifier<QColor> for an example
Defining a custom format for, say, console output only requires implementing a few `QAbstractValueWriter` abstract methods:
Defining a custom format for, say, console output only requires implementing a few `QAbstractValueWriter` abstract methods:
```cpp
```cpp
...
@@ -294,10 +294,10 @@ MyTextWriter(&ba) << 1.333333333333f << PI << ascii << false << color ;
...
@@ -294,10 +294,10 @@ MyTextWriter(&ba) << 1.333333333333f << PI << ascii << false << color ;
Implementing a custom reader requires implementing a few `QAbstractValueReader` methods. This is always more complex because reading
Implementing a custom reader requires implementing a few `QAbstractValueReader` methods. This is always more complex because reading
needs to perform much more checks and may have to report transient errors instead of returning `true` as `MyTextWriter` does.
needs to perform much more checks and may have to report transient errors instead of returning `true` as `MyTextWriter` does.
Both `QAbstractValueWriter` and `QAbstractValueReader` are convenient base classes which implement most of `QAbstractValue` interface used by QBind<T> with
Both `QAbstractValueWriter` and `QAbstractValueReader` are convenient base classes which implement most of `QAbstractValue` interface used by QTransmogrifier<T> with
generic text representations of boolean, numbers, etc.
generic text representations of boolean, numbers, etc.
Since QBind supports QVal<_> on the right-hand side, translating between generic data formats or structures is a one-liner:
Since QTransmogrifier supports QVal<_> on the right-hand side, translating between generic data formats or structures is a one-liner:
-`qmName` allows naming data items for, e.g. XML root element and sequence items
-`qmName` allows naming data items for, e.g. XML root element and sequence items
- ...
- ...
One can also customize binds using ad-hoc std::function like [flatten](tests/QBind/main.cpp#L996) or lambda below (mimicking Python list comprehensions):
One can also customize binds using ad-hoc std::function like [flatten](tests/QTransmogrifier/main.cpp#L996) or lambda below (mimicking Python list comprehensions):