Commit 24bfc174 authored by EXT Arnaud Clère's avatar EXT Arnaud Clère

Fixed custom bind safety with explicit Val/Seq/Rec ctors

Used fun ptr instead of dynamic std::function
Fixed template deduction casting non-capturing lambda to fun ptr
parent 0ddc8a49
......@@ -169,16 +169,18 @@ struct QBindDefault {
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 TResult>
using QBindSeqFunction = std::function<Seq<TResult>&&(Seq<TResult>&&)>;
// Custom bind support
template<typename T> using QBindFunction = Cursor (*)(T &,Val<Cursor>&&);
template<class Ts> using QBindSeqFunction = Seq<Cursor>&&(*)(Ts&,Seq<Cursor>&&);
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 Val
{
Q_DISABLE_COPY(Val)
public:
Q_ENABLE_MOVE_DEFAULT(Val)
Val(T_&& out) noexcept { std::swap(outer, out); }
explicit Val(T_&& out) noexcept { std::swap(outer, out); }
using TResult = typename T_::TResult; //!< the result of the traversal
......@@ -194,18 +196,20 @@ public:
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_ (); }
// Custom bind support
template<typename T> T_ bind (T& t, QBindFunction<T> customBind) { return customBind(t, std::move(unsafeThis())) ? std::move(outer) : T_(); }
// Literal metadata support
/**/ Val<T_> meta (QMetaData&& m) { return meta ( m); }
/**/ Seq<T_> sequence( quint32 s) { return sequence(&s); }
/**/ Rec<T_> record ( quint32 s) { return record (&s); }
// Sequence comprehension support
T_ bindSequence(QBindSeqFunction<TResult> customBind) { return sequence().bindFrom(customBind).out(); }
// Shortcuts
template<typename T> Seq<T_> operator<< ( T&& t) { return sequence().bind(std::forward<T>(t)); } // stream compatible
/**/ Rec<T_> record (QName n) { return meta({{qmName,n}}).record(); }
private:
Val<TResult> unsafeThis() noexcept { return Val<TResult>(outer->_unsafeCopy()); }
T_ outer = T_(); //!< moved context of current traversal up to TResult that will point to the value itself (be it a QIODevice or QCborValue)
};
......@@ -214,7 +218,7 @@ template<class T_> class Seq
Q_DISABLE_COPY(Seq)
public:
Q_ENABLE_MOVE_DEFAULT(Seq)
Seq(T_&& out) noexcept { std::swap(outer, out); }
explicit Seq(T_&& out) noexcept { std::swap(outer, out); }
// T_ can be either a TResult or any combination of nested Seq or Rec like Rec<Seq<TResult>
using TResult = typename T_::TResult;
......@@ -235,11 +239,10 @@ public:
template<typename T> Seq<T_> bind ( T&& t) { return item().bind(std::forward<T>(t)); }
template<typename T> Rec<T_> bind (T& t, T&& defaultT) { return item().bind(t,std::forward<T>(defaultT)); }
// Sequence comprehension support
template<class Ts>
Seq<T_> from (Ts&, TResult(*)(typename Ts::value_type&, Val<TResult>&&), bool(*)(const typename Ts::value_type&) = [](const typename Ts::value_type&) { return true; });
Seq<T_> bindFrom (QBindSeqFunction<TResult> customBind) { return bool(customBind(std::move(unsafeThis()))) ? std::move(*this) : Seq<T_>(); }
/**/T_ bindSequence(QBindSeqFunction<TResult> customBind) { return item().bindSequence(customBind); }
// Custom bind support
template<class Ts> Seq<T_> bind (Ts& ts, QBindSeqFunction<Ts> customBind) { return bool(customBind(ts, std::move(unsafeThis()))) ? std::move(*this) : Seq<T_>(); }
template<class Ts> Seq<T_> forEach (Ts& ts, TResult(*)( typename Ts::value_type&, Val<TResult>&&)
, bool (*)(const typename Ts::value_type&) = [](const typename Ts::value_type&) { return true; });
// Stream compatible shortcut
template<typename T> Seq<T_> operator<<(T&& t) { return item().bind(std::forward<T>(t)); }
......@@ -256,7 +259,7 @@ template<class T_> class Rec
Q_DISABLE_COPY(Rec)
public:
Q_ENABLE_MOVE_DEFAULT(Rec)
Rec(T_&& out) noexcept { std::swap(outer, out); }
explicit Rec(T_&& out) noexcept { std::swap(outer, out); }
// T_ can be either a TResult or any combination of nested Seq or Rec like Seq<Rec<TResult>
using TResult = typename T_::TResult;
......@@ -270,15 +273,15 @@ public:
Val<Rec<T_>> item(QUtf8String& n) { return outer->_item(n) ? Val<Rec<T_>>(std::move(*this)) : Val<Rec<T_>>(); }
Val<Rec<T_>> item( QName n) { return outer->_item(n) ? Val<Rec<T_>>(std::move(*this)) : Val<Rec<T_>>(); }
// Sequence comprehension support
T_ bindSequence(QName n, QBindSeqFunction<TResult> customBind) { return item(n).bindSequence(customBind).out(); }
// Shortcuts
/**/ Seq<Rec<T_>> sequence(QName n, quint32* s=nullptr) { return item(n).sequence ( s); }
/**/ Rec<Rec<T_>> record (QName n, quint32* s=nullptr) { return item(n).record ( s); }
/**/ Rec<T_> null (QName n ) { return item(n).null ( ); }
template<typename T> Rec<T_> bind (QName n, T&& t) { return item(n).bind(std::forward<T>(t)); }
template<typename T> Rec<T_> bind (QName n, T& t, T&& defaultT) { return item(n).bind(t,std::forward<T>(defaultT)); }
// Custom bind support
template<typename T> Rec<T_> bind (QName n, T& t, QBindFunction<T> customBind) { return item(n).bind(t, customBind); }
private:
template<typename T, class TResult, typename TEnabledIf> friend struct QBind;
Val<TResult> unsafeItem(QUtf8String& n) noexcept { return outer->_item(n) ? Val<TResult>(outer._unsafeCopy()) : Val<TResult>(); }
......@@ -316,7 +319,7 @@ public:
using TResult = Cur;
Cur(TImpl* i) : impl(i) { Q_ASSERT(impl); }
explicit Cur(TImpl* i) : impl(i) { Q_ASSERT(impl); }
BindMode mode() const noexcept { return impl ? impl->mode() : BindMode::Invalid; }
......@@ -774,9 +777,9 @@ public:
template<class T_>
template<class Ts>
Seq<T_> Seq<T_>::from(Ts& ts,
TResult(*itemBind)(typename Ts::value_type&, Val<TResult>&&),
bool(*itemPredicate)(const typename Ts::value_type&)) {
Seq<T_> Seq<T_>::forEach(Ts& ts,
TResult(*itemBind)(typename Ts::value_type&, Val<TResult>&&),
bool(*itemPredicate)(const typename Ts::value_type&)) {
if ((*this)->mode()==Write) {
for (auto&& t : ts) {
if (itemPredicate(t)) {
......@@ -1088,7 +1091,7 @@ struct QBind<Val<Cursor>> {
Val<Cursor> srcVal; Val<Cursor> dstVal;
while((srcVal = srcSeq.unsafeItem())) { // using item()'s Val<Seq<Cursor> would cause an infinite compiler loop to generate corresponding QBind<Val<Seq<Seq<...>>,_> functions
dstVal = dstSeq.unsafeItem();
srcSeq = srcVal.bind(std::move(dstVal));
srcSeq = Seq<Cursor>(srcVal.bind(std::move(dstVal)));
}
/**/ dstSeq.out();
......@@ -1101,7 +1104,7 @@ struct QBind<Val<Cursor>> {
QUtf8String srcKey; Val<Cursor> srcVal; Val<Cursor> dstVal;
while((srcVal = srcRec.unsafeItem(srcKey))) {
dstVal = dstRec.unsafeItem(srcKey);
srcRec = srcVal.bind(std::move(dstVal));
srcRec = Rec<Cursor>(srcVal.bind(std::move(dstVal)));
}
/**/ dstRec.out();
......
......@@ -64,9 +64,8 @@ class QModelWriter : public IWriter
public:
QModelWriter(QAbstractItemModel* m, bool rowFirst=true) : m(m), rowFirst(rowFirst), w(&io) { Q_ASSERT(m); }
/**/Cursor bindSequence(QBindSeqFunction<Cursor> customBind) { return Cursor(this).value().bindSequence(customBind); }
Seq<Cursor> sequence ( ) { return Cursor(this).value().sequence(); }
Val<Cursor> meta ( QMetaData&& m) { return Cursor(this).value().meta(m); }
Seq<Cursor> sequence( ) { return Cursor(this).value().sequence(); }
Val<Cursor> meta ( QMetaData&& m) { return Cursor(this).value().meta(m); }
protected:
void _meta(QMetaData& meta) {
if (T==d) {
......
......@@ -15,6 +15,8 @@ public:
Q_ENUM(Type)
Phone(Type t=Unknown, QString n=QString()) : _t(t), _n(n) {}
static bool isOffice(const Phone& phone) { return phone._t == Phone::Office; } // for custom bind example
//private:
Type _t; QString _n;
};
......
......@@ -952,16 +952,14 @@ int main(int argc, char *argv[])
return (fclose(samples)==0) &/*anyway*/ (fclose(results)==0);
}
Seq<Cursor>&& flatten(const QList<Person>& ps, Seq<Cursor>&& s) {
Seq<Cursor>&& flatten(QList<Person>& ps, Seq<Cursor>&& s) {
for (auto&& p : ps) { // Iterating on ps only works for Write but flatten cannot be Read/Write anyway
s = s
.record()
.bind("first name", p.firstName )
.bind("height(ft)", p.height*3.28)
.out()
.bindFrom([&](Seq<Cursor>&& s)->Seq<Cursor>&& {
return std::move(flatten(p.children, std::move(s)));
});
.bind(p.children, flatten);
}
return std::move(s);
};
......@@ -992,42 +990,40 @@ void doGuiExample() {
// Various possible designs for flexible custom bind
#if 0
// Safest design that works for Read/Write but requires several cumbersome functions
QModelWriter<>(&customModel).sequence().from(persons, [](Person& p, Val<Cursor>&& item)->Cursor { // ADL cannot deduce Person
// Design that works for Read/Write but requires several cumbersome functions
QModelWriter<>(&customModel).sequence().forEach(persons, [](Person& p, Val<Cursor>&& item)->Cursor {
return item
.record()
.bind("first name", p.firstName)
.sequence("phones").from(p.phones,
.sequence("phones").forEach(p.phones,
[](Phone& phone, Val<Cursor>&& item){
return item.bind(phone._n);
},
[](const Phone& phone){
return phone._t == Phone::Office;
}); // automagically closed while cast to returned Cursor type
Phone::isOffice)
; // automagically closed while cast to returned Cursor type
});
#else
// More powerful design similar to Python list comprehensions that does not work by default for Read/Write
QModelWriter<>(&customModel).bindSequence([&](Seq<Cursor>&& s)->Seq<Cursor>&& {
for (auto&& p : persons) { // Read would require looping while !s.item()
s = s // To keep working with the active Cursor
// More expressive design similar to Python list comprehensions that does not work by default for Read/Write
QModelWriter<>(&customModel).sequence().bind(persons, +[](QList<Person>& t, Seq<Cursor>&& s)->Seq<Cursor>&& { // See https://stackoverflow.com/questions/18889028/a-positive-lambda-what-sorcery-is-this
for (auto&& person : t) { // Read would require looping while !s.item()
s = s // To keep working with the active Cursor
.record()
.bind("first name", p.firstName)
.bindSequence("phones", [&](Seq<Cursor>&& s)->Seq<Cursor>&& {
for (auto&& phone : p.phones) {
if (phone._t == Phone::Office) {
s = s.bind(phone._n);
.bind("first name" , person.firstName)
.bind("office phone", person.phones , +[](QVector<Phone>& t, Val<Cursor>&& v)->Cursor {
for (auto&& phone : t) {
if (phone._t == Phone::Office) {
return v.bind(phone._n);
}
}
}
return std::move(s);
});
return v.null();
})
.out();
}
return std::move(s); // So caller stops calling IBind if user function was unable to keep track of the active Cursor
});
#endif
// The same design allows flattening trees into a sequence
QModelWriter<>(& flatModel).bindSequence([&](Seq<Cursor>&& s)->Seq<Cursor>&& {
return std::move(flatten(persons, std::move(s)));
});
QModelWriter<>(& flatModel).sequence().bind(persons, flatten);
QDialog dlg;
auto layout = new QHBoxLayout(&dlg); dlg.setLayout(layout);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment