QGIM: add reusable concepts for unifying pointer and reference types

Standardize the way to get a reference or pointer to an element, and use
that to implement begin/end helpers that work with values of and
pointers/references to ranges.

This allows us to support copies, references, raw pointers, and smart
pointers holding a range, both as the data itself and as rows of data.

Change-Id: Ief680d87fcfd6fcabcfe2fa4841d3641b49f0c43
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Artem Dyomin 2025-04-02 16:42:17 +02:00 committed by Volker Hilsheimer
parent ec200659da
commit 424d6ebf13
2 changed files with 192 additions and 96 deletions

View File

@ -28,7 +28,7 @@ public:
MultiColumn<T>>, bool>; MultiColumn<T>>, bool>;
template <typename V = T, template <typename V = T,
std::enable_if_t<std::is_constructible_v<bool, V>, bool> = true> std::enable_if_t<QGenericItemModelDetails::is_validatable<V>::value, bool> = true>
constexpr explicit operator bool() const noexcept { return bool(data); } constexpr explicit operator bool() const noexcept { return bool(data); }
// unconstrained on size_t I, gcc internal error #3280 // unconstrained on size_t I, gcc internal error #3280
@ -166,9 +166,9 @@ class QGenericItemModelImpl : public QGenericItemModelImplBase
{ {
Q_DISABLE_COPY_MOVE(QGenericItemModelImpl) Q_DISABLE_COPY_MOVE(QGenericItemModelImpl)
public: public:
using range_type = QGenericItemModelDetails::remove_ptr_and_ref_t<Range>; using range_type = QGenericItemModelDetails::wrapped_t<Range>;
using row_reference = decltype(*std::begin(std::declval<range_type&>())); using row_reference = decltype(*QGenericItemModelDetails::begin(std::declval<range_type&>()));
using const_row_reference = decltype(*std::cbegin(std::declval<range_type&>())); using const_row_reference = decltype(*QGenericItemModelDetails::cbegin(std::declval<range_type&>()));
using row_type = std::remove_reference_t<row_reference>; using row_type = std::remove_reference_t<row_reference>;
protected: protected:
@ -177,24 +177,29 @@ protected:
const Structure& that() const { return static_cast<const Structure &>(*this); } const Structure& that() const { return static_cast<const Structure &>(*this); }
template <typename C> template <typename C>
static constexpr auto size(const C &c) static constexpr int size(const C &c)
{ {
if constexpr (QGenericItemModelDetails::test_size<C>()) if (!QGenericItemModelDetails::isValid(c))
return 0;
if constexpr (QGenericItemModelDetails::test_size<C>()) {
return std::size(c); return std::size(c);
else } else {
#if defined(__cpp_lib_ranges) #if defined(__cpp_lib_ranges)
return std::ranges::distance(std::begin(c), std::end(c)); return int(std::ranges::distance(QGenericItemModelDetails::begin(c),
QGenericItemModelDetails::end(c)));
#else #else
return std::distance(std::begin(c), std::end(c)); return int(std::distance(QGenericItemModelDetails::begin(c),
QGenericItemModelDetails::end(c)));
#endif #endif
}
} }
friend class tst_QGenericItemModel; friend class tst_QGenericItemModel;
using range_features = QGenericItemModelDetails::range_traits<range_type>; using range_features = QGenericItemModelDetails::range_traits<range_type>;
using row_features = QGenericItemModelDetails::range_traits<row_type>; using wrapped_row_type = QGenericItemModelDetails::wrapped_t<row_type>;
using row_features = QGenericItemModelDetails::range_traits<wrapped_row_type>;
using row_traits = QGenericItemModelDetails::row_traits<q20::remove_cvref_t< using row_traits = QGenericItemModelDetails::row_traits<std::remove_cv_t<wrapped_row_type>>;
std::remove_pointer_t<row_type>>>;
static constexpr bool isMutable() static constexpr bool isMutable()
{ {
@ -204,7 +209,9 @@ protected:
} }
static constexpr int static_row_count = QGenericItemModelDetails::static_size_v<range_type>; static constexpr int static_row_count = QGenericItemModelDetails::static_size_v<range_type>;
static constexpr bool rows_are_pointers = std::is_pointer_v<row_type>; static constexpr bool rows_are_raw_pointers = std::is_pointer_v<row_type>;
static constexpr bool rows_are_owning_or_raw_pointers =
QGenericItemModelDetails::is_owning_or_raw_pointer<row_type>();
static constexpr int static_column_count = QGenericItemModelDetails::static_size_v<row_type>; static constexpr int static_column_count = QGenericItemModelDetails::static_size_v<row_type>;
static constexpr bool one_dimensional_range = static_column_count == 0; static constexpr bool one_dimensional_range = static_column_count == 0;
@ -213,8 +220,8 @@ protected:
// A row might be a value (or range of values), or a pointer. // A row might be a value (or range of values), or a pointer.
// row_ptr is always a pointer, and const_row_ptr is a pointer to const. // row_ptr is always a pointer, and const_row_ptr is a pointer to const.
using row_ptr = std::conditional_t<rows_are_pointers, row_type, row_type *>; using row_ptr = wrapped_row_type *;
using const_row_ptr = const std::remove_pointer_t<row_type> *; using const_row_ptr = const wrapped_row_type *;
template <typename T> template <typename T>
static constexpr bool has_metaobject = static constexpr bool has_metaobject =
@ -444,7 +451,7 @@ public:
if (index.isValid()) { if (index.isValid()) {
const_row_reference row = rowData(index); const_row_reference row = rowData(index);
if constexpr (dynamicColumns()) if constexpr (dynamicColumns())
readData(*std::next(std::cbegin(row), index.column())); readData(*QGenericItemModelDetails::cpos(row, index.column()));
else if constexpr (one_dimensional_range) else if constexpr (one_dimensional_range)
readData(row); readData(row);
else if (QGenericItemModelDetails::isValid(row)) else if (QGenericItemModelDetails::isValid(row))
@ -507,7 +514,7 @@ public:
if (index.isValid()) { if (index.isValid()) {
const_row_reference row = rowData(index); const_row_reference row = rowData(index);
if constexpr (dynamicColumns()) if constexpr (dynamicColumns())
readItemData(*std::next(std::cbegin(row), index.column())); readItemData(*QGenericItemModelDetails::cpos(row, index.column()));
else if constexpr (one_dimensional_range) else if constexpr (one_dimensional_range)
readItemData(row); readItemData(row);
else if (QGenericItemModelDetails::isValid(row)) else if (QGenericItemModelDetails::isValid(row))
@ -576,7 +583,7 @@ public:
row_reference row = rowData(index); row_reference row = rowData(index);
if constexpr (dynamicColumns()) { if constexpr (dynamicColumns()) {
success = writeData(*std::next(std::begin(row), index.column())); success = writeData(*QGenericItemModelDetails::pos(row, index.column()));
} else if constexpr (one_dimensional_range) { } else if constexpr (one_dimensional_range) {
success = writeData(row); success = writeData(row);
} else if (QGenericItemModelDetails::isValid(row)) { } else if (QGenericItemModelDetails::isValid(row)) {
@ -688,7 +695,7 @@ public:
row_reference row = rowData(index); row_reference row = rowData(index);
if constexpr (dynamicColumns()) { if constexpr (dynamicColumns()) {
success = writeItemData(*std::next(std::begin(row), index.column())); success = writeItemData(*QGenericItemModelDetails::pos(row, index.column()));
} else if constexpr (one_dimensional_range) { } else if constexpr (one_dimensional_range) {
success = writeItemData(row); success = writeItemData(row);
} else if (QGenericItemModelDetails::isValid(row)) { } else if (QGenericItemModelDetails::isValid(row)) {
@ -740,7 +747,7 @@ public:
row_reference row = rowData(index); row_reference row = rowData(index);
if constexpr (dynamicColumns()) { if constexpr (dynamicColumns()) {
success = clearData(*std::next(std::begin(row), index.column())); success = clearData(*QGenericItemModelDetails::pos(row, index.column()));
} else if constexpr (one_dimensional_range) { } else if constexpr (one_dimensional_range) {
success = clearData(row); success = clearData(row);
} else if (QGenericItemModelDetails::isValid(row)) { } else if (QGenericItemModelDetails::isValid(row)) {
@ -766,8 +773,10 @@ public:
return false; return false;
beginInsertColumns(parent, column, column + count - 1); beginInsertColumns(parent, column, column + count - 1);
for (auto &child : *children) for (auto &child : *children) {
child.insert(std::next(std::begin(child), column), count, {}); auto it = QGenericItemModelDetails::pos(child, column);
QGenericItemModelDetails::refTo(child).insert(it, count, {});
}
endInsertColumns(); endInsertColumns();
return true; return true;
} }
@ -786,8 +795,8 @@ public:
beginRemoveColumns(parent, column, column + count - 1); beginRemoveColumns(parent, column, column + count - 1);
for (auto &child : *children) { for (auto &child : *children) {
const auto start = std::next(std::begin(child), column); const auto start = QGenericItemModelDetails::pos(child, column);
child.erase(start, std::next(start, count)); QGenericItemModelDetails::refTo(child).erase(start, std::next(start, count));
} }
endRemoveColumns(); endRemoveColumns();
return true; return true;
@ -818,10 +827,9 @@ public:
} }
for (auto &child : *children) { for (auto &child : *children) {
const auto begin = std::begin(child); const auto first = QGenericItemModelDetails::pos(child, sourceColumn);
const auto first = std::next(begin, sourceColumn); const auto middle = std::next(first, count);
const auto middle = std::next(begin, sourceColumn + count); const auto last = QGenericItemModelDetails::pos(child, destColumn);
const auto last = std::next(begin, destColumn);
if (sourceColumn < destColumn) // moving right if (sourceColumn < destColumn) // moving right
std::rotate(first, middle, last); std::rotate(first, middle, last);
@ -849,12 +857,12 @@ public:
beginInsertRows(parent, row, row + count - 1); beginInsertRows(parent, row, row + count - 1);
const auto pos = std::next(std::begin(*children), row); const auto pos = QGenericItemModelDetails::pos(children, row);
if constexpr (range_features::has_insert_range) { if constexpr (range_features::has_insert_range) {
EmptyRowGenerator first{0, that(), parent}; EmptyRowGenerator first{0, that(), parent};
EmptyRowGenerator last{count, that(), parent}; EmptyRowGenerator last{count, that(), parent};
children->insert(pos, first, last); children->insert(pos, first, last);
} else if constexpr (rows_are_pointers) { } else if constexpr (rows_are_raw_pointers) {
auto start = children->insert(pos, count, nullptr); auto start = children->insert(pos, count, nullptr);
auto end = std::next(start, count); auto end = std::next(start, count);
for (auto it = start; it != end; ++it) for (auto it = start; it != end; ++it)
@ -862,7 +870,7 @@ public:
} else { } else {
children->insert(pos, count, that().makeEmptyRow(parent)); children->insert(pos, count, that().makeEmptyRow(parent));
} }
if constexpr (!rows_are_pointers) { if constexpr (!rows_are_raw_pointers) {
// fix the parent in all children of the modified row, as the // fix the parent in all children of the modified row, as the
// references back to the parent might have become invalid. // references back to the parent might have become invalid.
that().resetParentInChildren(children); that().resetParentInChildren(children);
@ -899,15 +907,15 @@ public:
} }
} }
{ // erase invalidates iterators { // erase invalidates iterators
const auto begin = std::next(std::begin(*children), row); const auto begin = QGenericItemModelDetails::pos(children, row);
const auto end = std::next(begin, count); const auto end = std::next(begin, count);
if constexpr (rows_are_pointers) if constexpr (rows_are_raw_pointers)
that().deleteRemovedRows(begin, end); that().deleteRemovedRows(begin, end);
children->erase(begin, end); children->erase(begin, end);
} }
// fix the parent in all children of the modified row, as the // fix the parent in all children of the modified row, as the
// references back to the parent might have become invalid. // references back to the parent might have become invalid.
if constexpr (!rows_are_pointers) if constexpr (!rows_are_raw_pointers)
that().resetParentInChildren(children); that().resetParentInChildren(children);
if constexpr (dynamicColumns()) { if constexpr (dynamicColumns()) {
if (callEndRemoveColumns) { if (callEndRemoveColumns) {
@ -945,16 +953,16 @@ public:
if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destParent, destRow)) if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destParent, destRow))
return false; return false;
const auto first = std::next(std::begin(*source), sourceRow); const auto first = QGenericItemModelDetails::pos(source, sourceRow);
const auto middle = std::next(first, count); const auto middle = std::next(first, count);
const auto last = std::next(std::begin(*source), destRow); const auto last = QGenericItemModelDetails::pos(source, destRow);
if (sourceRow < destRow) // moving down if (sourceRow < destRow) // moving down
std::rotate(first, middle, last); std::rotate(first, middle, last);
else // moving up else // moving up
std::rotate(last, first, middle); std::rotate(last, first, middle);
if constexpr (!rows_are_pointers) if constexpr (!rows_are_raw_pointers)
that().resetParentInChildren(source); that().resetParentInChildren(source);
endMoveRows(); endMoveRows();
@ -967,7 +975,7 @@ public:
protected: protected:
~QGenericItemModelImpl() ~QGenericItemModelImpl()
{ {
if constexpr (rows_are_pointers && !std::is_pointer_v<Range>) { if constexpr (rows_are_raw_pointers && !std::is_pointer_v<Range>) {
// If data with rows as pointers was moved in, then we own it and // If data with rows as pointers was moved in, then we own it and
// have to delete those rows. // have to delete those rows.
using ref = decltype(std::forward<Range>(std::declval<range_type>())); using ref = decltype(std::forward<Range>(std::declval<range_type>()));
@ -1212,7 +1220,7 @@ protected:
const_row_ptr grandParent = static_cast<const_row_ptr>(parent.constInternalPointer()); const_row_ptr grandParent = static_cast<const_row_ptr>(parent.constInternalPointer());
const auto &parentSiblings = childrenOf(grandParent); const auto &parentSiblings = childrenOf(grandParent);
const auto it = std::next(std::cbegin(parentSiblings), parent.row()); const auto it = QGenericItemModelDetails::cpos(parentSiblings, parent.row());
return this->createIndex(row, column, QGenericItemModelDetails::pointerTo(*it)); return this->createIndex(row, column, QGenericItemModelDetails::pointerTo(*it));
} }
@ -1228,19 +1236,20 @@ protected:
// get the siblings of the parent via the grand parent // get the siblings of the parent via the grand parent
const_row_ptr grandParent; const_row_ptr grandParent;
if constexpr (Base::rows_are_pointers) if constexpr (Base::rows_are_raw_pointers)
grandParent = m_protocol.parentRow(parentRow); grandParent = m_protocol.parentRow(parentRow);
else else
grandParent = m_protocol.parentRow(*parentRow); grandParent = m_protocol.parentRow(*parentRow);
const range_type &parentSiblings = childrenOf(grandParent); const range_type &parentSiblings = childrenOf(grandParent);
// find the index of parentRow // find the index of parentRow
const auto begin = std::cbegin(parentSiblings); const auto begin = QGenericItemModelDetails::cbegin(parentSiblings);
const auto end = std::cend(parentSiblings); const auto end = QGenericItemModelDetails::cend(parentSiblings);
const auto it = std::find_if(begin, end, [parentRow](auto &&s){ const auto it = std::find_if(begin, end, [parentRow](auto &&s){
return QGenericItemModelDetails::pointerTo(s) == parentRow; return QGenericItemModelDetails::pointerTo(s) == parentRow;
}); });
if (it != end) if (it != end)
return this->createIndex(std::distance(begin, it), 0, grandParent); return this->createIndex(std::distance(begin, it), 0,
QGenericItemModelDetails::pointerTo(grandParent));
return {}; return {};
} }
@ -1266,7 +1275,7 @@ protected:
// We must not insert rows if we cannot adjust the parents of the // We must not insert rows if we cannot adjust the parents of the
// children of the following rows. We don't have to do that if the // children of the following rows. We don't have to do that if the
// range operates on pointers. // range operates on pointers.
return (Base::rows_are_pointers || tree_traits::has_setParentRow) return (Base::rows_are_raw_pointers || tree_traits::has_setParentRow)
&& Base::dynamicRows() && range_features::has_insert; && Base::dynamicRows() && range_features::has_insert;
} }
@ -1275,7 +1284,7 @@ protected:
// We must not remove rows if we cannot adjust the parents of the // We must not remove rows if we cannot adjust the parents of the
// children of the following rows. We don't have to do that if the // children of the following rows. We don't have to do that if the
// range operates on pointers. // range operates on pointers.
return (Base::rows_are_pointers || tree_traits::has_setParentRow) return (Base::rows_are_raw_pointers || tree_traits::has_setParentRow)
&& Base::dynamicRows() && range_features::has_erase; && Base::dynamicRows() && range_features::has_erase;
} }
@ -1295,7 +1304,7 @@ protected:
// If rows are pointers, then reference to the parent row don't // If rows are pointers, then reference to the parent row don't
// change, so we can move them around freely. Otherwise we need to // change, so we can move them around freely. Otherwise we need to
// be able to explicitly update the parent pointer. // be able to explicitly update the parent pointer.
if constexpr (!Base::rows_are_pointers && !tree_traits::has_setParentRow) { if constexpr (!Base::rows_are_raw_pointers && !tree_traits::has_setParentRow) {
return false; return false;
} else if constexpr (!(range_features::has_insert && range_features::has_erase)) { } else if constexpr (!(range_features::has_insert && range_features::has_erase)) {
return false; return false;
@ -1309,9 +1318,9 @@ protected:
// If we can insert data from another range into, then // If we can insert data from another range into, then
// use that to move the old data over. // use that to move the old data over.
const auto destStart = std::next(std::begin(*destination), destRow); const auto destStart = QGenericItemModelDetails::pos(destination, destRow);
if constexpr (range_features::has_insert_range) { if constexpr (range_features::has_insert_range) {
const auto sourceStart = std::next(std::begin(*source), sourceRow); const auto sourceStart = QGenericItemModelDetails::pos(*source, sourceRow);
const auto sourceEnd = std::next(sourceStart, count); const auto sourceEnd = std::next(sourceStart, count);
destination->insert(destStart, std::move_iterator(sourceStart), destination->insert(destStart, std::move_iterator(sourceStart),
@ -1339,9 +1348,9 @@ protected:
// move the data over and update the parent pointer // move the data over and update the parent pointer
{ {
const auto writeStart = std::next(std::begin(*destination), destRow); const auto writeStart = QGenericItemModelDetails::pos(destination, destRow);
const auto writeEnd = std::next(writeStart, count); const auto writeEnd = std::next(writeStart, count);
const auto sourceStart = std::next(std::begin(*source), sourceRow); const auto sourceStart = QGenericItemModelDetails::pos(source, sourceRow);
const auto sourceEnd = std::next(sourceStart, count); const auto sourceEnd = std::next(sourceStart, count);
for (auto write = writeStart, read = sourceStart; write != writeEnd; ++write, ++read) { for (auto write = writeStart, read = sourceStart; write != writeEnd; ++write, ++read) {
@ -1359,7 +1368,7 @@ protected:
// ranges, as the references to the entries might have become invalid. // ranges, as the references to the entries might have become invalid.
// We don't have to do that if the rows are pointers, as in that case // We don't have to do that if the rows are pointers, as in that case
// the references to the entries are stable. // the references to the entries are stable.
if constexpr (!Base::rows_are_pointers) { if constexpr (!Base::rows_are_raw_pointers) {
resetParentInChildren(destination); resetParentInChildren(destination);
resetParentInChildren(source); resetParentInChildren(source);
} }
@ -1384,8 +1393,8 @@ protected:
template <typename R> template <typename R>
void destroyOwnedModel(R &&range) void destroyOwnedModel(R &&range)
{ {
const auto begin = std::begin(range); const auto begin = QGenericItemModelDetails::begin(range);
const auto end = std::end(range); const auto end = QGenericItemModelDetails::end(range);
deleteRemovedRows(begin, end); deleteRemovedRows(begin, end);
} }
@ -1399,8 +1408,8 @@ protected:
void resetParentInChildren(range_type *children) void resetParentInChildren(range_type *children)
{ {
if constexpr (tree_traits::has_setParentRow) { if constexpr (tree_traits::has_setParentRow) {
const auto begin = std::begin(*children); const auto begin = QGenericItemModelDetails::begin(*children);
const auto end = std::end(*children); const auto end = QGenericItemModelDetails::end(*children);
for (auto it = begin; it != end; ++it) { for (auto it = begin; it != end; ++it) {
if (auto &maybeChildren = m_protocol.childRows(*it)) { if (auto &maybeChildren = m_protocol.childRows(*it)) {
QModelIndexList fromIndexes; QModelIndexList fromIndexes;
@ -1431,7 +1440,7 @@ protected:
const_row_ptr parentRow = static_cast<const_row_ptr>(index.constInternalPointer()); const_row_ptr parentRow = static_cast<const_row_ptr>(index.constInternalPointer());
const range_type &siblings = childrenOf(parentRow); const range_type &siblings = childrenOf(parentRow);
Q_ASSERT(index.row() < int(Base::size(siblings))); Q_ASSERT(index.row() < int(Base::size(siblings)));
return *(std::next(std::cbegin(siblings), index.row())); return *QGenericItemModelDetails::cpos(siblings, index.row());
} }
decltype(auto) rowDataImpl(const QModelIndex &index) decltype(auto) rowDataImpl(const QModelIndex &index)
@ -1439,13 +1448,13 @@ protected:
row_ptr parentRow = static_cast<row_ptr>(index.internalPointer()); row_ptr parentRow = static_cast<row_ptr>(index.internalPointer());
range_type &siblings = childrenOf(parentRow); range_type &siblings = childrenOf(parentRow);
Q_ASSERT(index.row() < int(Base::size(siblings))); Q_ASSERT(index.row() < int(Base::size(siblings)));
return *(std::next(std::begin(siblings), index.row())); return *QGenericItemModelDetails::pos(siblings, index.row());
} }
auto childRangeImpl(const QModelIndex &index) const auto childRangeImpl(const QModelIndex &index) const
{ {
const auto &row = this->rowData(index); const auto &row = this->rowData(index);
if constexpr (Base::rows_are_pointers) { if constexpr (Base::rows_are_raw_pointers) {
if (!row) if (!row)
return static_cast<const range_type *>(nullptr); return static_cast<const range_type *>(nullptr);
} }
@ -1456,7 +1465,7 @@ protected:
auto childRangeImpl(const QModelIndex &index) auto childRangeImpl(const QModelIndex &index)
{ {
auto &row = this->rowData(index); auto &row = this->rowData(index);
if constexpr (Base::rows_are_pointers) { if constexpr (Base::rows_are_raw_pointers) {
if (!row) if (!row)
return static_cast<range_type *>(nullptr); return static_cast<range_type *>(nullptr);
} }
@ -1471,7 +1480,7 @@ protected:
{ {
if (!row) if (!row)
return *this->m_data.model(); return *this->m_data.model();
if constexpr (Base::rows_are_pointers) if constexpr (Base::rows_are_raw_pointers)
return *m_protocol.childRows(row); return *m_protocol.childRows(row);
else else
return *m_protocol.childRows(*row); return *m_protocol.childRows(*row);
@ -1482,7 +1491,7 @@ private:
{ {
if (!row) if (!row)
return *this->m_data.model(); return *this->m_data.model();
if constexpr (Base::rows_are_pointers) if constexpr (Base::rows_are_raw_pointers)
return *m_protocol.childRows(row); return *m_protocol.childRows(row);
else else
return *m_protocol.childRows(*row); return *m_protocol.childRows(*row);
@ -1515,7 +1524,7 @@ protected:
QModelIndex indexImpl(int row, int column, const QModelIndex &) const QModelIndex indexImpl(int row, int column, const QModelIndex &) const
{ {
if constexpr (Base::dynamicColumns()) { if constexpr (Base::dynamicColumns()) {
if (column < int(Base::size(*std::next(std::cbegin(*this->m_data.model()), row)))) if (column < int(Base::size(*QGenericItemModelDetails::cpos(*this->m_data.model(), row))))
return this->createIndex(row, column); return this->createIndex(row, column);
// if we got here, then column < columnCount(), but this row is to short // if we got here, then column < columnCount(), but this row is to short
qCritical("QGenericItemModel: Column-range at row %d is not large enough!", row); qCritical("QGenericItemModel: Column-range at row %d is not large enough!", row);
@ -1546,7 +1555,7 @@ protected:
if constexpr (Base::dynamicColumns()) { if constexpr (Base::dynamicColumns()) {
return int(Base::size(*this->m_data.model()) == 0 return int(Base::size(*this->m_data.model()) == 0
? 0 ? 0
: Base::size(*std::cbegin(*this->m_data.model()))); : Base::size(*QGenericItemModelDetails::cbegin(*this->m_data.model())));
} else if constexpr (Base::one_dimensional_range) { } else if constexpr (Base::one_dimensional_range) {
return row_traits::fixed_size(); return row_traits::fixed_size();
} else { } else {
@ -1602,8 +1611,8 @@ protected:
template <typename R> template <typename R>
void destroyOwnedModel(R &&range) void destroyOwnedModel(R &&range)
{ {
const auto begin = std::begin(range); const auto begin = QGenericItemModelDetails::begin(range);
const auto end = std::end(range); const auto end = QGenericItemModelDetails::end(range);
for (auto it = begin; it != end; ++it) for (auto it = begin; it != end; ++it)
delete *it; delete *it;
} }
@ -1617,13 +1626,13 @@ protected:
decltype(auto) rowDataImpl(const QModelIndex &index) const decltype(auto) rowDataImpl(const QModelIndex &index) const
{ {
Q_ASSERT(index.row() < int(Base::size(*this->m_data.model()))); Q_ASSERT(index.row() < int(Base::size(*this->m_data.model())));
return *(std::next(std::cbegin(*this->m_data.model()), index.row())); return *QGenericItemModelDetails::cpos(*this->m_data.model(), index.row());
} }
decltype(auto) rowDataImpl(const QModelIndex &index) decltype(auto) rowDataImpl(const QModelIndex &index)
{ {
Q_ASSERT(index.row() < int(Base::size(*this->m_data.model()))); Q_ASSERT(index.row() < int(Base::size(*this->m_data.model())));
return *(std::next(std::begin(*this->m_data.model()), index.row())); return *QGenericItemModelDetails::pos(*this->m_data.model(), index.row());
} }
auto childRangeImpl(const QModelIndex &) const auto childRangeImpl(const QModelIndex &) const

View File

@ -30,14 +30,93 @@ QT_BEGIN_NAMESPACE
namespace QGenericItemModelDetails namespace QGenericItemModelDetails
{ {
template <typename T, size_t N> static decltype(auto) refTo(T (&t)[N]) { return t; } template <typename T, template <typename...> typename... Templates>
template <typename T> static T&& refTo(T&& t) { return std::forward<T>(t); } struct is_any_of_impl : std::false_type {};
template <typename T> static T& refTo(std::reference_wrapper<T> t) { return t.get(); }
// template <typename T> static auto refTo(T& t) -> decltype(*t) { return *t; }
template <typename T> static auto pointerTo(T *t) { return t; } template <template <typename...> typename Template,
template <typename T> static auto pointerTo(T &t) { return std::addressof(refTo(t)); } typename... Params,
template <typename T> static auto pointerTo(const T &&t) = delete; template <typename...> typename... Templates>
struct is_any_of_impl<Template<Params...>, Template, Templates...> : std::true_type {};
template <typename T,
template <typename...> typename Template,
template <typename...> typename... Templates>
struct is_any_of_impl<T, Template, Templates...> : is_any_of_impl<T, Templates...> {};
template <typename T, template <typename...> typename... Templates>
using is_any_of = is_any_of_impl<std::remove_cv_t<T>, Templates...>;
template <typename T>
using is_validatable = std::is_constructible<bool, T>;
template <typename T, typename = void>
struct is_smart_ptr : std::false_type {};
template <typename T>
struct is_smart_ptr<T,
std::enable_if_t<std::conjunction_v<
std::is_pointer<decltype(std::declval<T&>().get())>,
std::is_same<decltype(*std::declval<T&>().get()), decltype(*std::declval<T&>())>,
is_validatable<T>
>>>
: std::true_type
{};
// TODO: shouldn't we check is_smart_ptr && !is_copy_constructible && !is_copy_assignable
// to support users-specific ptrs?
template <typename T>
using is_any_unique_ptr = is_any_of<T, std::unique_ptr, QScopedPointer>;
template <typename T>
using is_any_shared_ptr = is_any_of<T, std::shared_ptr, QSharedPointer,
QExplicitlySharedDataPointer, QSharedDataPointer>;
template <typename T>
using is_owning_or_raw_pointer = std::disjunction<is_any_shared_ptr<T>, is_any_unique_ptr<T>, std::is_pointer<T>>;
template <typename T>
static auto pointerTo(T&& t) {
using Type = q20::remove_cvref_t<T>;
if constexpr (is_any_of<Type, std::optional>())
return t ? std::addressof(*std::forward<T>(t)) : nullptr;
else if constexpr (std::is_pointer<Type>())
return t;
else if constexpr (is_smart_ptr<Type>())
return t.get();
else if constexpr (is_any_of<Type, std::reference_wrapper>())
return std::addressof(t.get());
else
return std::addressof(std::forward<T>(t));
}
template <typename T>
using wrapped_t = std::remove_pointer_t<decltype(pointerTo(std::declval<T&>()))>;
template <typename T>
using is_wrapped = std::negation<std::is_same<wrapped_t<T>, std::remove_reference_t<T>>>;
template <typename T>
static constexpr bool isValid(const T &t) noexcept
{
if constexpr (is_validatable<T>())
return bool(t);
else
return true;
}
template <typename T>
static decltype(auto) refTo(T&& t) {
Q_ASSERT(isValid(t));
// it's allowed to move only if the object holds unique ownership of the wrapped data
using Type = q20::remove_cvref_t<T>;
if constexpr (is_any_of<T, std::optional>())
return *std::forward<T>(t); // let std::optional resolve dereferencing
if constexpr (!is_wrapped<Type>() || is_any_unique_ptr<Type>())
return q23::forward_like<T>(*pointerTo(t));
else
return *pointerTo(t);
}
template <typename It> template <typename It>
auto key(It&& it) -> decltype(it.key()) { return it.key(); } auto key(It&& it) -> decltype(it.key()) { return it.key(); }
@ -49,14 +128,25 @@ namespace QGenericItemModelDetails
template <typename It> template <typename It>
auto value(It&& it) -> decltype((it->second)) { return it->second; } auto value(It&& it) -> decltype((it->second)) { return it->second; }
template <typename T> // use our own version of begin/end so that we can overload for pointers
static constexpr bool isValid(const T &t) noexcept template <typename C>
{ static auto begin(C &&c) -> decltype(std::begin(refTo(std::forward<C>(c))))
if constexpr (std::is_constructible_v<bool, T>) { return std::begin(refTo(std::forward<C>(c))); }
return bool(t); template <typename C>
else static auto end(C &&c) -> decltype(std::end(refTo(std::forward<C>(c))))
return true; { return std::end(refTo(std::forward<C>(c))); }
} template <typename C>
static auto cbegin(C &&c) -> decltype(std::cbegin(refTo(std::forward<C>(c))))
{ return std::cbegin(refTo(std::forward<C>(c))); }
template <typename C>
static auto cend(C &&c) -> decltype(std::cend(refTo(std::forward<C>(c))))
{ return std::cend(refTo(std::forward<C>(c))); }
template <typename C>
static auto pos(C &&c, int i)
{ return std::next(QGenericItemModelDetails::begin(std::forward<C>(c)), i); }
template <typename C>
static auto cpos(C &&c, int i)
{ return std::next(QGenericItemModelDetails::cbegin(std::forward<C>(c)), i); }
// Test if a type is a range, and whether we can modify it using the // Test if a type is a range, and whether we can modify it using the
// standard C++ container member functions insert, erase, and resize. // standard C++ container member functions insert, erase, and resize.
@ -147,12 +237,13 @@ namespace QGenericItemModelDetails
static constexpr bool has_resize = false; static constexpr bool has_resize = false;
}; };
template <typename C> template <typename C>
struct range_traits<C, std::void_t<decltype(std::cbegin(std::declval<C&>())), struct range_traits<C, std::void_t<decltype(cbegin(std::declval<C&>())),
decltype(std::cend(std::declval<C&>())), decltype(cend(std::declval<C&>())),
std::enable_if_t<!is_multi_role_v<C>> std::enable_if_t<!is_multi_role_v<C>>
>> : std::true_type >> : std::true_type
{ {
using value_type = std::remove_reference_t<decltype(*std::begin(std::declval<C&>()))>; using iterator = decltype(begin(std::declval<C&>()));
using value_type = std::remove_reference_t<decltype(*std::declval<iterator&>())>;
static constexpr bool is_mutable = !std::is_const_v<C> && !std::is_const_v<value_type>; static constexpr bool is_mutable = !std::is_const_v<C> && !std::is_const_v<value_type>;
static constexpr bool has_insert = test_insert<C>(); static constexpr bool has_insert = test_insert<C>();
static constexpr bool has_insert_range = test_insert_range<C>(); static constexpr bool has_insert_range = test_insert_range<C>();
@ -180,10 +271,6 @@ namespace QGenericItemModelDetails
template <typename T> struct range_traits<const T *> : iterable_value<Mutable::No> {}; template <typename T> struct range_traits<const T *> : iterable_value<Mutable::No> {};
template <> struct range_traits<QLatin1StringView> : iterable_value<Mutable::No> {}; template <> struct range_traits<QLatin1StringView> : iterable_value<Mutable::No> {};
template <typename T>
using remove_ptr_and_ref_t =
std::remove_pointer_t<std::remove_reference_t<decltype(refTo(std::declval<T&>()))>>;
template <typename C> template <typename C>
[[maybe_unused]] static constexpr bool is_range_v = range_traits<C>(); [[maybe_unused]] static constexpr bool is_range_v = range_traits<C>();
@ -239,7 +326,7 @@ namespace QGenericItemModelDetails
template <typename T> template <typename T>
[[maybe_unused]] static constexpr int static_size_v = [[maybe_unused]] static constexpr int static_size_v =
row_traits<q20::remove_cvref_t<std::remove_pointer_t<T>>>::static_size; row_traits<std::remove_cv_t<wrapped_t<T>>>::static_size;
// tests for tree protocol implementation in the row type // tests for tree protocol implementation in the row type
template <typename R, typename = void> template <typename R, typename = void>
@ -375,18 +462,18 @@ namespace QGenericItemModelDetails
}; };
template <typename C, typename TreeProtocol = void, template <typename C, typename TreeProtocol = void,
typename range_type = remove_ptr_and_ref_t<C>> typename range_type = wrapped_t<C>>
using tree_protocol_t = typename tree_traits< using tree_protocol_t = typename tree_traits<
range_type, range_type,
std::remove_reference_t<decltype(*std::begin(std::declval<range_type&>()))>, std::remove_reference_t<decltype(*std::begin(std::declval<range_type&>()))>,
TreeProtocol>::tree_protocol; TreeProtocol>::tree_protocol;
template <typename C, typename range_type = remove_ptr_and_ref_t<C>> template <typename C, typename range_type = wrapped_t<C>>
using if_is_table_range = std::enable_if_t< using if_is_table_range = std::enable_if_t<
is_range_v<range_type> && std::is_void_v<tree_protocol_t<range_type>>, is_range_v<range_type> && std::is_void_v<tree_protocol_t<range_type>>,
bool>; bool>;
template <typename C, typename Protocol = void, typename range_type = remove_ptr_and_ref_t<C>> template <typename C, typename Protocol = void, typename range_type = wrapped_t<C>>
using if_is_tree_range = std::enable_if_t< using if_is_tree_range = std::enable_if_t<
is_range_v<range_type> && !std::is_void_v<tree_protocol_t<range_type, Protocol>>, is_range_v<range_type> && !std::is_void_v<tree_protocol_t<range_type, Protocol>>,
bool>; bool>;
@ -427,7 +514,7 @@ protected:
template <typename T, typename F> template <typename T, typename F>
static auto for_element_at(T &&tuple, size_t idx, F &&function) static auto for_element_at(T &&tuple, size_t idx, F &&function)
{ {
using type = std::remove_pointer_t<std::remove_reference_t<T>>; using type = QGenericItemModelDetails::wrapped_t<T>;
constexpr size_t size = std::tuple_size_v<type>; constexpr size_t size = std::tuple_size_v<type>;
Q_ASSERT(idx < size); Q_ASSERT(idx < size);
return call_at(std::forward<T>(tuple), idx, std::make_index_sequence<size>{}, return call_at(std::forward<T>(tuple), idx, std::make_index_sequence<size>{},
@ -444,7 +531,7 @@ protected:
template <typename T> template <typename T>
static constexpr QMetaType meta_type_at(size_t idx) static constexpr QMetaType meta_type_at(size_t idx)
{ {
using type = std::remove_pointer_t<std::remove_reference_t<T>>; using type = QGenericItemModelDetails::wrapped_t<T>;
constexpr auto size = std::tuple_size_v<type>; constexpr auto size = std::tuple_size_v<type>;
Q_ASSERT(idx < size); Q_ASSERT(idx < size);
return makeMetaTypes<type>(std::make_index_sequence<size>{}).at(idx); return makeMetaTypes<type>(std::make_index_sequence<size>{}).at(idx);