JNI: Constrain QJniArray::toContainer to compatible target containers

We can populate a container with the contents of the Java array as long
as the element types are convertible without narrowing (taking various
special cases into account, such as jstring to QString). The container
has to be either support emplace_back, or be a contiguous container
if a primitive element type.

The template helpers need to be in QJniArrayBase in order for qdoc to
accept the input.

Add test coverage, including static compile time tests.

Change-Id: Id9372deed5cf33446ee1969dc284a88991db2aee
Reviewed-by: Petri Virkkunen <petri.virkkunen@qt.io>
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
(cherry picked from commit 8f04defa1e3973faec19a9cb1ab9bbf1ea7fb031)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Volker Hilsheimer 2024-07-14 16:27:01 +02:00 committed by Qt Cherry-pick Bot
parent c21612d246
commit 5797326b16
3 changed files with 62 additions and 10 deletions

View File

@ -181,6 +181,13 @@ class QJniArrayBase
>
> : std::true_type {};
template <typename C, typename = void> struct HasEmplaceBackTest : std::false_type {};
template <typename C> struct HasEmplaceBackTest<C,
std::void_t<decltype(std::declval<C>().emplace_back(std::declval<typename C::value_type>()))>
> : std::true_type
{};
protected:
// these are used in QJniArray
template <typename C, typename = void>
@ -205,6 +212,24 @@ protected:
template <typename From, typename To>
using unless_convertible = std::enable_if_t<!QtPrivate::AreArgumentsConvertibleWithoutNarrowingBase<From, To>::value, bool>;
// helpers for toContainer
template <typename E> struct ToContainerHelper { using type = QList<E>; };
template <> struct ToContainerHelper<jstring> { using type = QStringList; };
template <> struct ToContainerHelper<jbyte> { using type = QByteArray; };
template <> struct ToContainerHelper<char> { using type = QByteArray; };
template <typename E>
using ToContainerType = typename ToContainerHelper<E>::type;
template <typename E, typename CRef, typename C = q20::remove_cvref_t<CRef>>
static constexpr bool isCompatibleTargetContainer =
(QtPrivate::AreArgumentsConvertibleWithoutNarrowingBase<E, typename C::value_type>::value
|| QtPrivate::AreArgumentsConvertibleWithoutNarrowingBase<typename ToContainerType<E>::value_type,
typename C::value_type>::value
|| (std::is_base_of_v<QtJniTypes::JObjectBase, E> && std::is_same_v<typename C::value_type, QString>))
&& (qxp::is_detected_v<HasEmplaceBackTest, C>
|| (isContiguousContainer<C> && ElementTypeHelper<C>::isPrimitive));
public:
using size_type = jsize;
using difference_type = size_type;
@ -236,6 +261,8 @@ public:
template <typename C>
using if_compatible_source_container = std::enable_if_t<isCompatibleSourceContainer<C>, bool>;
template <typename T, typename C>
using if_compatible_target_container = std::enable_if_t<isCompatibleTargetContainer<T, C>, bool>;
template <typename Container, if_compatible_source_container<Container> = true>
static auto fromContainer(Container &&container)
@ -332,13 +359,10 @@ class QJniArray : public QJniArrayBase
{
friend struct QJniArrayIterator<T>;
template <typename E> struct ToContainerHelper { using type = QList<E>; };
template <> struct ToContainerHelper<jstring> { using type = QStringList; };
template <> struct ToContainerHelper<jbyte> { using type = QByteArray; };
template <> struct ToContainerHelper<char> { using type = QByteArray; };
template <typename E>
using ToContainerType = typename ToContainerHelper<E>::type;
template <typename C>
using CanReserveTest = decltype(std::declval<C>().reserve(0));
template <typename C>
static constexpr bool canReserve = qxp::is_detected_v<CanReserveTest, C>;
public:
using Type = T;
@ -480,7 +504,7 @@ public:
}
}
template <typename Container = ToContainerType<T>>
template <typename Container = ToContainerType<T>, if_compatible_target_container<T, Container> = true>
Container toContainer(Container &&container = {}) const
{
const qsizetype sz = size();
@ -490,6 +514,7 @@ public:
using ContainerType = q20::remove_cvref_t<Container>;
if constexpr (canReserve<ContainerType>)
container.reserve(sz);
if constexpr (std::is_same_v<typename ContainerType::value_type, QString>) {
for (auto element : *this) {

View File

@ -410,7 +410,7 @@
*/
/*!
\fn template <typename T> template <typename Container> Container QJniArray<T>::toContainer(Container &&container) const
\fn template <typename T> template <typename Container, QJniArrayBase::if_compatible_target_container<T, Container>> Container QJniArray<T>::toContainer(Container &&container) const
\return a container populated with the data in the wrapped Java array.

View File

@ -367,6 +367,29 @@ void tst_QJniArray::ordering()
QCOMPARE_GT(arrayEnd, arrayBegin);
}
template <typename T, typename C>
using ToContainerTest = decltype(std::declval<QJniArray<T>>().toContainer(std::declval<C>()));
template <typename T, typename C>
static constexpr bool hasToContainer = qxp::is_detected_v<ToContainerTest, T, C>;
static_assert(hasToContainer<jint, QList<jint>>);
static_assert(hasToContainer<jint, QList<int>>);
static_assert(hasToContainer<jbyte, QByteArray>);
static_assert(hasToContainer<jstring, QStringList>);
static_assert(hasToContainer<String, QStringList>);
static_assert(hasToContainer<jchar, std::list<jchar>>);
static_assert(hasToContainer<jbyte, std::vector<jbyte>>);
// different types but doesn't narrow
static_assert(hasToContainer<jshort, QList<int>>);
static_assert(hasToContainer<jfloat, QList<jdouble>>);
// would narrow
static_assert(!hasToContainer<jlong, QList<short>>);
static_assert(!hasToContainer<jdouble, QList<jfloat>>);
// incompatible types
static_assert(!hasToContainer<jstring, QByteArray>);
void tst_QJniArray::toContainer()
{
std::vector<jchar> charVector{u'a', u'b', u'c'};
@ -377,6 +400,10 @@ void tst_QJniArray::toContainer()
QCOMPARE(vector, charVector);
QCOMPARE(charArray.toContainer<std::vector<jchar>>(), charVector);
// non-contiguous container of primitive elements
std::list charList = charArray.toContainer<std::list<jchar>>();
QCOMPARE(charList.size(), charVector.size());
}
void tst_QJniArray::pointerToValue()