diff --git a/src/corelib/itemmodels/qgenericitemmodel.cpp b/src/corelib/itemmodels/qgenericitemmodel.cpp index 0408f6dea55..6419958199e 100644 --- a/src/corelib/itemmodels/qgenericitemmodel.cpp +++ b/src/corelib/itemmodels/qgenericitemmodel.cpp @@ -172,9 +172,79 @@ QT_BEGIN_NAMESPACE \note The implementation of \c{get} above requires C++23. A C++17 compliant implementation can be found in the unit test code for QGenericItemModel. + Types that have a meta objects, and implement the C++ tuple protocol, also + can cause compile-time ambiguity when used as the row type, as the framework + won't know which API to use to access the individual values. Use the + QGenericItemModel::SingleColumn and QGenericItemModel::MultiColumns wrapper + to disambiguate. + \sa {Model/View Programming} */ +/*! + \typedef QGenericItemModel::SingleColumn + + Use this type to disambiguate when using the type \c{T} as the row type in + the range. If \c{T} provides a metaobject, then the framework will by + default represent the type as multiple columns, resulting in a table model. + + \snippet qgenericitemmodel/main.cpp color_gadget_0 + + When stored in a sequential range, this type will be interpreted as + multi-column rows with each property being one column. The range will be + represented as a table. + + \code + QList colors = { + // ... + }; + QGenericItemModel tableModel(colors); // columnCount() == 3 + \endcode + + When wrapped into QGenericItemModel::SingleColumn, the model will be a list, + with each instance of \c{T} represented as an item with multiple roles. + + \code + QList> colors = { + // ... + }; + QGenericItemModel listModel(colors); // columnCount() == 1 + \endcode + + \sa QGenericItemModel::MultiColumn +*/ + +/*! + \class QGenericItemModel::MultiColumn + \brief Represents the wrapped type \c{T} as multiple columns in a QGenericItemModel. + \inmodule QtCore + \ingroup model-view + \since 6.10 + + Use this type to disambiguate when the type \c{T} has both a metaobject, and + implements \l{the C++ tuple protocol}. The type will be represented as + multiple columns, and the individual values will be accessed through the + tuple protocol. + + \snippet qgenericitemmodel/main.cpp color_gadget_0 + \code + namespace std { + template <> struct tuple_size : integral_constant {}; + // ... + } + + QList> colors = { + // ... + }; + QGenericItemModel colorList(colors); + \endcode + + To represent the type a single column value with multiple roles, use + QGenericItemModel::SingleColumn instead. + + \sa QGenericItemModel::SingleColumn +*/ + /*! \fn template > QGenericItemModel::QGenericItemModel(Range &&range, QObject *parent) diff --git a/src/corelib/itemmodels/qgenericitemmodel.h b/src/corelib/itemmodels/qgenericitemmodel.h index 05941f4a890..15afcecd701 100644 --- a/src/corelib/itemmodels/qgenericitemmodel.h +++ b/src/corelib/itemmodels/qgenericitemmodel.h @@ -16,6 +16,32 @@ public: template using SingleColumn = std::tuple; + template + struct MultiColumn + { + using type = std::remove_pointer_t; + T data{}; + template + using if_get_matches = std::enable_if_t, + MultiColumn>, bool>; + + template , bool> = true> + constexpr explicit operator bool() const noexcept { return bool(data); } + + // unconstrained on size_t I, gcc internal error #3280 + template = true> + friend inline decltype(auto) get(V &&multiColumn) + { + static_assert(I < std::tuple_size_v, "Index out of bounds for wrapped type"); + Q_ASSERT(multiColumn); + if constexpr (std::is_pointer_v) + return get(*multiColumn.data); + else + return get(q23::forward_like(multiColumn.data)); + } + }; + template = true> explicit QGenericItemModel(Range &&range, QObject *parent = nullptr); @@ -1057,4 +1083,15 @@ QGenericItemModel::QGenericItemModel(Range &&range, QObject *parent) QT_END_NAMESPACE +namespace std { + template + struct tuple_size> + : tuple_size::type> + {}; + template + struct tuple_element> + : tuple_element::type> + {}; +} + #endif // QGENERICITEMMODEL_H diff --git a/src/corelib/itemmodels/qgenericitemmodel_impl.h b/src/corelib/itemmodels/qgenericitemmodel_impl.h index b5974e212e5..1048df779e1 100644 --- a/src/corelib/itemmodels/qgenericitemmodel_impl.h +++ b/src/corelib/itemmodels/qgenericitemmodel_impl.h @@ -198,7 +198,7 @@ namespace QGenericItemModelDetails template static auto pointerTo(const T &&t) = delete; template - static bool isValid(T &&t) + static constexpr bool isValid(T &&t) noexcept { if constexpr (std::is_constructible_v) return bool(t); diff --git a/tests/auto/corelib/itemmodels/qgenericitemmodel/tst_qgenericitemmodel.cpp b/tests/auto/corelib/itemmodels/qgenericitemmodel/tst_qgenericitemmodel.cpp index 64c06d2232d..c30264d01b7 100644 --- a/tests/auto/corelib/itemmodels/qgenericitemmodel/tst_qgenericitemmodel.cpp +++ b/tests/auto/corelib/itemmodels/qgenericitemmodel/tst_qgenericitemmodel.cpp @@ -65,6 +65,38 @@ private: int m_number = 42; }; +// a class that can be both and requires disambiguation +class MetaObjectTuple : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString display MEMBER m_string) + Q_PROPERTY(int number MEMBER m_number) +public: + using QObject::QObject; + +private: + QString m_string = "4321"; + int m_number = 24; + + template = true, + std::enable_if_t, MetaObjectTuple>, bool> = true + > + friend inline decltype(auto) get(G &&item) + { + if constexpr (I == 0) + return q23::forward_like(item.m_string); + else if constexpr (I == 1) + return q23::forward_like(item.m_number); + } +}; + +namespace std { + template <> struct tuple_size : std::integral_constant {}; + template <> struct tuple_element<0, MetaObjectTuple> { using type = QString; }; + template <> struct tuple_element<1, MetaObjectTuple> { using type = int; }; +} + struct Row { Item m_item; @@ -200,6 +232,23 @@ private: &row1, &row2, &row3 }; + MetaObjectTuple mot1; + MetaObjectTuple mot2; + MetaObjectTuple mot3; + std::vector> listOfMetaObjectTuple = { + &mot1, + &mot2, + &mot3, + }; + MetaObjectTuple mot4; + MetaObjectTuple mot5; + MetaObjectTuple mot6; + std::vector> tableOfMetaObjectTuple = { + {&mot4}, + {&mot5}, + {&mot6}, + }; + // bad (but legal) get() overload that never returns a mutable reference std::vector vectorOfConstStructs = { {"one"}, @@ -364,6 +413,10 @@ void tst_QGenericItemModel::createTestData() << 1 << (ChangeAction::ChangeRows | ChangeAction::SetData | ChangeAction::SetItemData); ADD_COPY(listOfObjects) << 2 << (ChangeAction::ChangeRows | ChangeAction::SetData); + ADD_COPY(listOfMetaObjectTuple) + << 1 << (ChangeAction::ChangeRows | ChangeAction::SetData | ChangeAction::SetItemData); + ADD_COPY(tableOfMetaObjectTuple) + << 2 << (ChangeAction::ChangeRows | ChangeAction::SetData); ADD_COPY(tableOfNumbers) << 5 << ChangeActions(ChangeAction::All); @@ -694,6 +747,8 @@ void tst_QGenericItemModel::insertRows() QEXPECT_FAIL("tableOfPointersPointer", "No item created", Continue); QEXPECT_FAIL("tableOfRowPointersPointer", "No row created", Continue); QEXPECT_FAIL("listOfObjectsCopy", "No object created", Continue); + QEXPECT_FAIL("listOfMetaObjectTupleCopy", "No object created", Continue); + QEXPECT_FAIL("tableOfMetaObjectTupleCopy", "No object created", Continue); // associative containers are default constructed with no valid data ignoreFailureFromAssociativeContainers();