diff options
| author | Volker Hilsheimer <volker.hilsheimer@qt.io> | 2024-07-14 13:16:11 +0200 |
|---|---|---|
| committer | Volker Hilsheimer <volker.hilsheimer@qt.io> | 2024-07-19 22:17:01 +0200 |
| commit | 71be7834e67216010dc74ed855dc7b513e302f1a (patch) | |
| tree | 639cf1244cfc47f372d19d46f8e5a98fdf9210a4 /src | |
| parent | ebbf7b0fdf866190cd20e62d6b13c7c6808101da (diff) | |
JNI: Improve the constraint on QJniArray::fromContainer
We can create a QJniArray from any container that has a forward
iterator. A contiguous container can be used to optimize the code path,
as long as the element type is primitive (i.e. not an object type).
Fixes: QTBUG-126151
Pick-to: 6.8
Change-Id: I21915f944d226d6d4f1113a54e5602ddc9cd727e
Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
Diffstat (limited to 'src')
| -rw-r--r-- | src/corelib/kernel/qjniarray.h | 74 | ||||
| -rw-r--r-- | src/corelib/kernel/qjniarray.qdoc | 14 | ||||
| -rw-r--r-- | src/corelib/kernel/qjniobject.h | 14 |
3 files changed, 83 insertions, 19 deletions
diff --git a/src/corelib/kernel/qjniarray.h b/src/corelib/kernel/qjniarray.h index 2cb5a98128a..f63433253da 100644 --- a/src/corelib/kernel/qjniarray.h +++ b/src/corelib/kernel/qjniarray.h @@ -151,6 +151,28 @@ private: class QJniArrayBase { // for SFINAE'ing out the fromContainer named constructor + template <typename C, typename = void> struct IsSequentialContainerHelper : std::false_type + { + static constexpr bool isForwardIterable = false; + }; + template <typename C> + struct IsSequentialContainerHelper<C, std::void_t<typename std::iterator_traits<typename C::const_iterator>::iterator_category, + typename C::value_type, + decltype(std::size(std::declval<C>())) + > + > : std::true_type + { + static constexpr bool isForwardIterable = std::is_convertible_v< + typename std::iterator_traits<typename C::const_iterator>::iterator_category, + std::forward_iterator_tag + >; + }; + template <> + struct IsSequentialContainerHelper<QByteArray, void> + { + static constexpr bool isForwardIterable = true; + }; + template <typename C, typename = void> struct IsContiguousContainerHelper : std::false_type {}; template <typename C> struct IsContiguousContainerHelper<C, std::void_t<decltype(std::data(std::declval<C>())), @@ -161,6 +183,23 @@ class QJniArrayBase protected: // these are used in QJniArray + template <typename C, typename = void> + struct ElementTypeHelper + { + static constexpr bool isObject = false; + static constexpr bool isPrimitive = false; + }; + template <typename C> + struct ElementTypeHelper<C, std::void_t<typename C::value_type>> + { + using E = typename C::value_type; + static constexpr bool isObject = QtJniTypes::isObjectType<E>(); + static constexpr bool isPrimitive = QtJniTypes::isPrimitiveType<E>(); + }; + + template <typename CRef, typename C = q20::remove_cvref_t<CRef>> + static constexpr bool isContiguousContainer = IsContiguousContainerHelper<C>::value; + template <typename From, typename To> using if_convertible = std::enable_if_t<QtPrivate::AreArgumentsConvertibleWithoutNarrowingBase<From, To>::value, bool>; template <typename From, typename To> @@ -184,12 +223,21 @@ public: return 0; } + // We can create an array from any forward-iterable container, and optimize + // for contiguous containers holding primitive elements. QJniArray is a + // forward-iterable container, so explicitly remove that from the overload + // set so that the copy constructors get used instead. + // Used also in the deduction guide, so must be public + template <typename CRef, typename C = q20::remove_cvref_t<CRef>> + static constexpr bool isCompatibleSourceContainer = + (IsSequentialContainerHelper<C>::isForwardIterable + || (isContiguousContainer<C> && ElementTypeHelper<C>::isPrimitive)) + && !std::is_base_of_v<QJniArrayBase, C>; + template <typename C> - static constexpr bool isContiguousContainer = IsContiguousContainerHelper<q20::remove_cvref_t<C>>::value; - template <typename C> - using if_contiguous_container = std::enable_if_t<isContiguousContainer<C>, bool>; + using if_compatible_source_container = std::enable_if_t<isCompatibleSourceContainer<C>, bool>; - template <typename Container, if_contiguous_container<Container> = true> + template <typename Container, if_compatible_source_container<Container> = true> static auto fromContainer(Container &&container) { Q_ASSERT_X(size_t(std::size(container)) <= size_t((std::numeric_limits<size_type>::max)()), @@ -337,7 +385,7 @@ public: template <typename Other, unless_convertible<Other, T> = true> QJniArray(QJniArray<Other> &&other) noexcept = delete; - template <typename Container, if_contiguous_container<Container> = true> + template <typename Container, if_compatible_source_container<Container> = true> explicit QJniArray(Container &&container) : QJniArrayBase(QJniArrayBase::fromContainer(std::forward<Container>(container))) { @@ -453,7 +501,7 @@ public: } else if constexpr (std::is_base_of_v<std::remove_pointer_t<jobject>, std::remove_pointer_t<T>>) { for (auto element : *this) container.emplace_back(element); - } else if constexpr (QJniArrayBase::isContiguousContainer<ContainerType>) { + } else if constexpr (isContiguousContainer<ContainerType>) { container.resize(sz); if constexpr (QtJniTypes::sameTypeForJni<T, jbyte>) { env->GetByteArrayRegion(object<jbyteArray>(), @@ -496,7 +544,7 @@ public: // fromContainer() maps several C++ types to the same JNI type (e.g. both jboolean and // bool become QJniArray<jboolean>), we have to deduce to what fromContainer() would // give us. -template <typename Container, QJniArrayBase::if_contiguous_container<Container> = true> +template <typename Container, QJniArrayBase::if_compatible_source_container<Container> = true> QJniArray(Container) -> QJniArray<typename decltype(QJniArrayBase::fromContainer(std::declval<Container>()))::value_type>; template <typename ElementType, typename List, typename NewFn, typename SetFn> @@ -508,10 +556,16 @@ auto QJniArrayBase::makeArray(List &&list, NewFn &&newArray, SetFn &&setRegion) if (QJniEnvironment::checkAndClearExceptions(env)) return QJniArray<ElementType>(); - // can't use static_cast here because we have signed/unsigned mismatches if (length) { - (env->*setRegion)(localArray, 0, length, - reinterpret_cast<const ElementType *>(std::data(std::as_const(list)))); + // can't use static_cast here because we have signed/unsigned mismatches + if constexpr (isContiguousContainer<List>) { + (env->*setRegion)(localArray, 0, length, + reinterpret_cast<const ElementType *>(std::data(std::as_const(list)))); + } else { + size_type i = 0; + for (const auto &e : std::as_const(list)) + (env->*setRegion)(localArray, i++, 1, reinterpret_cast<const ElementType *>(&e)); + } } return QJniArray<ElementType>(localArray); }; diff --git a/src/corelib/kernel/qjniarray.qdoc b/src/corelib/kernel/qjniarray.qdoc index 4bc38d6c22a..727f60e6497 100644 --- a/src/corelib/kernel/qjniarray.qdoc +++ b/src/corelib/kernel/qjniarray.qdoc @@ -16,15 +16,15 @@ */ /*! - \fn template <typename Container, QJniArrayBase::if_contiguous_container<Container>> auto QJniArrayBase::fromContainer(Container &&container) + \fn template <typename Container, QJniArrayBase::if_compatible_source_container<Container>> auto QJniArrayBase::fromContainer(Container &&container) Creates a Java array holding the data in \a container, and returns a QJniArray instance that wraps it. -//! [contiguous-containers] +//! [forward-iterable-containers] This function only participates in overload resolution if \c{Container} - is a contiguous container storing elements of a \l{JNI types}{JNI type} - or equivalent C++ type. + is a container that stores elements of a \l{JNI types}{JNI type} or equivalent + C++ type, and provides a forward iterator. The specialization of the constructed QJniArray depends on the value type of the \a container. For a \c{Container<T>} (such as e.g. \c{QList<T>}) it @@ -47,7 +47,7 @@ \li QJniObject \li QJniArray<jobject> \endtable -//! [contiguous-containers] +//! [forward-iterable-containers] \sa QJniArray::toContainer() */ @@ -278,13 +278,13 @@ */ /*! - \fn template <typename T> template <typename Container, QJniArrayBase::if_contiguous_container<Container>> QJniArray<T>::QJniArray(Container &&container) + \fn template <typename T> template <typename Container, QJniArrayBase::if_compatible_source_container<Container>> QJniArray<T>::QJniArray(Container &&container) Constructs a QJniArray that wraps a newly-created Java array for elements of type \c{Container::value_type}, and populates the Java array with the data from \a container. - \include qjniarray.qdoc contiguous-containers + \include qjniarray.qdoc forward-iterable-containers \sa QJniArrayBase::fromContainer(), toContainer() */ diff --git a/src/corelib/kernel/qjniobject.h b/src/corelib/kernel/qjniobject.h index e3168488fc7..c84effc160c 100644 --- a/src/corelib/kernel/qjniobject.h +++ b/src/corelib/kernel/qjniobject.h @@ -848,6 +848,16 @@ QT_END_NAMESPACE #include <QtCore/qjniarray.h> QT_BEGIN_NAMESPACE +namespace QtJniTypes { +namespace detail { +template <typename C> +using FromContainerTest = decltype(QJniArrayBase::fromContainer(std::declval<C>())); + +template <typename C> +static constexpr bool isCompatibleSourceContainer = qxp::is_detected_v<FromContainerTest, C>; +} +} + template <typename ...Args> template <typename T> auto QJniObject::LocalFrame<Args...>::convertToJni(T &&value) @@ -857,7 +867,7 @@ auto QJniObject::LocalFrame<Args...>::convertToJni(T &&value) return newLocalRef<jstring>(QJniObject::fromString(value)); } else if constexpr (QtJniTypes::IsJniArray<Type>::value) { return value.arrayObject(); - } else if constexpr (QJniArrayBase::isContiguousContainer<T>) { + } else if constexpr (QtJniTypes::detail::isCompatibleSourceContainer<T>) { using QJniArrayType = decltype(QJniArrayBase::fromContainer(std::forward<T>(value))); using ArrayType = decltype(std::declval<QJniArrayType>().arrayObject()); return newLocalRef<ArrayType>(QJniArrayBase::fromContainer(std::forward<T>(value)).template object<jobject>()); @@ -878,7 +888,7 @@ auto QJniObject::LocalFrame<Args...>::convertFromJni(QJniObject &&object) return object.toString(); } else if constexpr (QtJniTypes::IsJniArray<Type>::value) { return T(std::move(object)); - } else if constexpr (QJniArrayBase::isContiguousContainer<Type>) { + } else if constexpr (QtJniTypes::detail::isCompatibleSourceContainer<Type>) { // if we were to create a QJniArray from Type... using QJniArrayType = decltype(QJniArrayBase::fromContainer(std::declval<Type>())); // then that QJniArray would have elements of type |
