From a7d2855b3c7716467c827f27359259afb5c4cbea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Nordheim?= Date: Tue, 27 Jun 2023 18:29:38 +0200 Subject: [PATCH] invokeMethod: enable passing parameters to overload taking functors This was missing for a while, and there is nothing fundamentally missing for it to work. With the more recent work around slot objects and invokeMethod in general, it is a good time to add support for this. In this patch, when connecting to a functor, it automatically deduces the overload to call based on the arguments passed to invokeMethod. Sharing code with QObject::connect could be done, but they have a key difference that makes it harder: With signal emissions we throw away trailing arguments that are not used: i.e. `signal(int, int)` can be connected to `slot(int)` or `slot()`. With invokeMethod that's not a thing. So we will need a way to toggle that behavior during resolution. [ChangeLog][QtCore][QMetaObject] Added support for passing parameters to the overload of QMetaObject::invokeMethod that takes a functor. These new overloads must have the return-value passed through qReturnArg(). Change-Id: If4fcbb75515b19e72fab80115c109efa37e6626e Reviewed-by: Ievgenii Meshcheriakov Reviewed-by: Thiago Macieira --- src/corelib/compat/removed_api.cpp | 7 ++ src/corelib/kernel/qmetaobject.cpp | 48 ++++++- src/corelib/kernel/qobjectdefs.h | 118 ++++++++++++++++-- src/corelib/kernel/qobjectdefs_impl.h | 57 +++++++-- src/corelib/kernel/qtimer.cpp | 4 +- .../kernel/qmetaobject/tst_qmetaobject.cpp | 112 +++++++++++++++++ 6 files changed, 318 insertions(+), 28 deletions(-) diff --git a/src/corelib/compat/removed_api.cpp b/src/corelib/compat/removed_api.cpp index 088b418aef..a504bef17d 100644 --- a/src/corelib/compat/removed_api.cpp +++ b/src/corelib/compat/removed_api.cpp @@ -635,6 +635,13 @@ QString QLocale::bcp47Name() const return bcp47Name(TagSeparator::Dash); } +#include "qobjectdefs.h" + +bool QMetaObject::invokeMethodImpl(QObject *object, QtPrivate::QSlotObjectBase *slot, Qt::ConnectionType type, void *ret) +{ + return invokeMethodImpl(object, slot, type, 1, &ret, nullptr, nullptr); +} + #include "qurl.h" QUrl QUrl::fromEncoded(const QByteArray &input, ParsingMode mode) diff --git a/src/corelib/kernel/qmetaobject.cpp b/src/corelib/kernel/qmetaobject.cpp index c6ab79718b..7445974404 100644 --- a/src/corelib/kernel/qmetaobject.cpp +++ b/src/corelib/kernel/qmetaobject.cpp @@ -1612,12 +1612,16 @@ bool QMetaObject::invokeMethodImpl(QObject *obj, const char *member, Qt::Connect return printMethodNotFoundWarning(obj->metaObject(), name, paramCount, typeNames, metaTypes); } -bool QMetaObject::invokeMethodImpl(QObject *object, QtPrivate::QSlotObjectBase *slotObj, - Qt::ConnectionType type, void *ret) +bool QMetaObject::invokeMethodImpl(QObject *object, QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type, + qsizetype parameterCount, const void *const *params, const char *const *names, + const QtPrivate::QMetaTypeInterface * const *metaTypes) { + // We don't need this now but maybe we want it later, or we may be able to + // share more code between the two invokeMethodImpl() overloads: + Q_UNUSED(names); auto slot = QtPrivate::SlotObjUniquePtr(slotObj); - if (! object) + if (! object) // ### only if the slot requires the object + not queued? return false; Qt::HANDLE currentThreadId = QThread::currentThreadId(); @@ -1629,8 +1633,7 @@ bool QMetaObject::invokeMethodImpl(QObject *object, QtPrivate::QSlotObjectBase * if (type == Qt::AutoConnection) type = receiverInSameThread ? Qt::DirectConnection : Qt::QueuedConnection; - void *argv[] = { ret }; - + void **argv = const_cast(params); if (type == Qt::DirectConnection) { slot->call(object, argv); } else if (type == Qt::QueuedConnection) { @@ -1639,8 +1642,16 @@ bool QMetaObject::invokeMethodImpl(QObject *object, QtPrivate::QSlotObjectBase * "queued connections"); return false; } + auto event = std::make_unique(std::move(slot), nullptr, -1, parameterCount); + void **args = event->args(); + QMetaType *types = event->types(); - QCoreApplication::postEvent(object, new QMetaCallEvent(std::move(slot), nullptr, -1, 1)); + for (int i = 1; i < parameterCount; ++i) { + types[i] = QMetaType(metaTypes[i]); + args[i] = types[i].create(argv[i]); + } + + QCoreApplication::postEvent(object, event.release()); } else if (type == Qt::BlockingQueuedConnection) { #if QT_CONFIG(thread) if (receiverInSameThread) @@ -1733,6 +1744,31 @@ bool QMetaObject::invokeMethodImpl(QObject *object, QtPrivate::QSlotObjectBase * Qt::AutoConnection will be used. */ +/*! + \fn template bool QMetaObject::invokeMethod(QObject *context, Functor &&function, Qt::ConnectionType type, QTemplatedMetaMethodReturnArgument ret, Args &&...arguments) + \fn template bool QMetaObject::invokeMethod(QObject *context, Functor &&function, QTemplatedMetaMethodReturnArgument ret, Args &&...arguments) + \fn template bool QMetaObject::invokeMethod(QObject *context, Functor &&function, Qt::ConnectionType type, Args &&...arguments) + \fn template bool QMetaObject::invokeMethod(QObject *context, Functor &&function, Args &&...arguments) + + \since 6.7 + \threadsafe + + Invokes the \a function with \a arguments in the event loop of \a context. + \a function can be a functor or a pointer to a member function. Returns + \c true if the function could be invoked. The return value of the + function call is placed in \a ret. The object used for the \a ret argument + should be obtained by passing your object to qReturnArg(). For example: + + \badcode + MyClass *obj = ...; + int result = 0; + QMetaObject::invokeMethod(obj, &MyClass::myMethod, qReturnArg(result), parameter); + \endcode + + If \a type is set, then the function is invoked using that connection type. + Otherwise, Qt::AutoConnection will be used. +*/ + /*! \fn QMetaObject::Connection &QMetaObject::Connection::operator=(Connection &&other) diff --git a/src/corelib/kernel/qobjectdefs.h b/src/corelib/kernel/qobjectdefs.h index e024695a0b..71e7f00697 100644 --- a/src/corelib/kernel/qobjectdefs.h +++ b/src/corelib/kernel/qobjectdefs.h @@ -416,33 +416,101 @@ struct Q_CORE_EXPORT QMetaObject static bool invokeMethod(QObject *context, Functor &&function, Qt::ConnectionType type = Qt::AutoConnection, FunctorReturnType *ret = nullptr); template static bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret); + + template + static bool invokeMethod(QObject *context, Functor &&function, Qt::ConnectionType type, QTemplatedMetaMethodReturnArgument ret, Args &&...arguments); + template + static bool invokeMethod(QObject *context, Functor &&function, QTemplatedMetaMethodReturnArgument ret, Args &&...arguments); + template + static bool invokeMethod(QObject *context, Functor &&function, Qt::ConnectionType type, Args &&...arguments); + template + static bool invokeMethod(QObject *context, Functor &&function, Args &&...arguments); #else template static std::enable_if_t, QtPrivate::Invoke::AreOldStyleArgs>, bool> invokeMethod(typename QtPrivate::ContextTypeForFunctor::ContextType *object, - Func &&function, - Qt::ConnectionType type = Qt::AutoConnection, - typename QtPrivate::Callable::ReturnType *ret = nullptr) + Func &&function, Qt::ConnectionType type, + typename QtPrivate::Callable::ReturnType *ret) { - static_assert(QtPrivate::Callable::ArgumentCount <= 0, - "QMetaObject::invokeMethod cannot call functions with arguments!"); - using Prototype = typename QtPrivate::Callable::Function; - return invokeMethodImpl(object, QtPrivate::makeCallableObject(std::forward(function)), type, ret); + using R = typename QtPrivate::Callable::ReturnType; + const auto getReturnArg = [ret]() -> QTemplatedMetaMethodReturnArgument { + if constexpr (std::is_void_v) + return {}; + else + return ret ? qReturnArg(*ret) : QTemplatedMetaMethodReturnArgument{}; + }; + return invokeMethod(object, std::forward(function), type, getReturnArg()); } - template static std::enable_if_t, QtPrivate::Invoke::AreOldStyleArgs>, bool> invokeMethod(typename QtPrivate::ContextTypeForFunctor::ContextType *object, - Func &&function, - typename QtPrivate::Callable::ReturnType *ret) + Func &&function, typename QtPrivate::Callable::ReturnType *ret) { return invokeMethod(object, std::forward(function), Qt::AutoConnection, ret); } + template + static std::enable_if_t, + QtPrivate::Invoke::AreOldStyleArgs>, + bool> + invokeMethod(typename QtPrivate::ContextTypeForFunctor::ContextType *object, + Func &&function, Qt::ConnectionType type, + QTemplatedMetaMethodReturnArgument< + typename QtPrivate::Callable::ReturnType> + ret, + Args &&...args) + { + return invokeMethodCallableHelper(object, std::forward(function), type, ret, + std::forward(args)...); + } + + template + static std::enable_if_t, + QtPrivate::Invoke::AreOldStyleArgs>, + bool> + invokeMethod(typename QtPrivate::ContextTypeForFunctor::ContextType *object, + Func &&function, Qt::ConnectionType type, Args &&...args) + { + using R = typename QtPrivate::Callable::ReturnType; + QTemplatedMetaMethodReturnArgument r{ QtPrivate::qMetaTypeInterfaceForType(), nullptr, + nullptr }; + return invokeMethod(object, std::forward(function), type, r, + std::forward(args)...); + } + + template + static std::enable_if_t, + QtPrivate::Invoke::AreOldStyleArgs>, + bool> + invokeMethod(typename QtPrivate::ContextTypeForFunctor::ContextType *object, + Func &&function, + QTemplatedMetaMethodReturnArgument< + typename QtPrivate::Callable::ReturnType> + ret, + Args &&...args) + { + return invokeMethod(object, std::forward(function), Qt::AutoConnection, ret, + std::forward(args)...); + } + + template + static std::enable_if_t, + QtPrivate::Invoke::AreOldStyleArgs>, + bool> + invokeMethod(typename QtPrivate::ContextTypeForFunctor::ContextType *object, + Func &&function, Args &&...args) + { + using R = typename QtPrivate::Callable::ReturnType; + QTemplatedMetaMethodReturnArgument r{ QtPrivate::qMetaTypeInterfaceForType(), nullptr, + nullptr }; + return invokeMethod(object, std::forward(function), Qt::AutoConnection, r, + std::forward(args)...); + } + #endif #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) @@ -530,10 +598,40 @@ struct Q_CORE_EXPORT QMetaObject } d; private: + // Just need to have this here with a separate name so the other inline + // functions can call this without any ambiguity + template + static bool + invokeMethodCallableHelper(typename QtPrivate::ContextTypeForFunctor::ContextType *object, + Func &&function, Qt::ConnectionType type, const QMetaMethodReturnArgument &ret, + Args &&...args) + { + using Callable = QtPrivate::Callable; + using ExpectedArguments = typename Callable::Arguments; + static_assert(sizeof...(Args) <= ExpectedArguments::size, "Too many arguments"); + using ActualArguments = QtPrivate::List; + static_assert(QtPrivate::CheckCompatibleArguments::value, + "Incompatible arguments"); + + auto h = QtPrivate::invokeMethodHelper(ret, args...); + + auto callable = new QtPrivate::QCallableObject, ActualArguments, + typename Callable::ReturnType>(std::forward(function)); + return invokeMethodImpl(object, callable, type, h.parameterCount(), h.parameters.data(), + h.typeNames.data(), h.metaTypes.data()); + } + static bool invokeMethodImpl(QObject *object, const char *member, Qt::ConnectionType type, qsizetype parameterCount, const void *const *parameters, const char *const *names, const QtPrivate::QMetaTypeInterface * const *metaTypes); + static bool invokeMethodImpl(QObject *object, QtPrivate::QSlotObjectBase *slotObj, + Qt::ConnectionType type, qsizetype parameterCount, + const void *const *params, const char *const *names, + const QtPrivate::QMetaTypeInterface *const *metaTypes); +#if QT_CORE_REMOVED_SINCE(6, 7) static bool invokeMethodImpl(QObject *object, QtPrivate::QSlotObjectBase *slot, Qt::ConnectionType type, void *ret); +#endif static QObject *newInstanceImpl(const QMetaObject *mobj, qsizetype parameterCount, const void **parameters, const char **typeNames, const QtPrivate::QMetaTypeInterface **metaTypes); diff --git a/src/corelib/kernel/qobjectdefs_impl.h b/src/corelib/kernel/qobjectdefs_impl.h index a45792c5d7..72928f2235 100644 --- a/src/corelib/kernel/qobjectdefs_impl.h +++ b/src/corelib/kernel/qobjectdefs_impl.h @@ -340,20 +340,55 @@ namespace QtPrivate { } }; - template - struct ZeroArgFunctor : Functor + template + struct FunctorCallable : Functor { - using ReturnType = decltype(std::declval()()); - using Function = ReturnType(*)(); - enum {ArgumentCount = 0}; - using Arguments = QtPrivate::List<>; + using ReturnType = decltype(std::declval()(std::declval()...)); + using Function = ReturnType(*)(Args...); + enum {ArgumentCount = sizeof...(Args)}; + using Arguments = QtPrivate::List; }; - template - using Callable = std::conditional_t>::ArgumentCount == -1, - ZeroArgFunctor>, - FunctionPointer> - >; + template + struct HasCallOperatorAcceptingArgs + { + private: + template + struct Test : std::false_type + { + }; + // We explicitly use .operator() to not return true for pointers to free/static function + template + struct Test().operator()(std::declval()...))>> + : std::true_type + { + }; + + public: + using Type = Test; + static constexpr bool value = Type::value; + }; + + template + constexpr bool + HasCallOperatorAcceptingArgs_v = HasCallOperatorAcceptingArgs::value; + + template + struct CallableHelper + { + private: + // Could've been std::conditional_t, but that requires all branches to + // be valid + static auto Resolve(std::true_type CallOperator) -> FunctorCallable; + static auto Resolve(std::false_type CallOperator) -> FunctionPointer>; + + public: + using Type = decltype(Resolve(typename HasCallOperatorAcceptingArgs, + Args...>::Type{})); + }; + + template + using Callable = typename CallableHelper::Type; /* Wrapper around ComputeFunctorArgumentCount and CheckCompatibleArgument, diff --git a/src/corelib/kernel/qtimer.cpp b/src/corelib/kernel/qtimer.cpp index 6487eb472e..4502e20a42 100644 --- a/src/corelib/kernel/qtimer.cpp +++ b/src/corelib/kernel/qtimer.cpp @@ -366,8 +366,10 @@ void QTimer::singleShotImpl(int msec, Qt::TimerType timerType, deleteReceiver = true; } + auto h = QtPrivate::invokeMethodHelper({}); QMetaObject::invokeMethodImpl(const_cast(receiver), slotObj, - Qt::QueuedConnection, nullptr); + Qt::QueuedConnection, h.parameterCount(), h.parameters.data(), h.typeNames.data(), + h.metaTypes.data()); if (deleteReceiver) const_cast(receiver)->deleteLater(); diff --git a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp index 926f3d1d60..8ccb5929a1 100644 --- a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp +++ b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp @@ -31,6 +31,8 @@ Q_DECLARE_METATYPE(const QMetaObject *) # define Q_NO_ARG #endif +using namespace Qt::StringLiterals; + struct MyStruct { int i; @@ -1081,6 +1083,98 @@ void tst_QMetaObject::invokePointer() QCOMPARE(obj.slotResult, QString("sl1:1")); } QCOMPARE(countedStructObjectsCount, 0); + + // Invoking with parameters + QString result; + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sl1, qReturnArg(result), u"bubu"_s)); + QCOMPARE(obj.slotResult, u"sl1:bubu"); + QCOMPARE(result, u"yessir"); + + // without taking return value + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sl1, u"bubu"_s)); + QCOMPARE(obj.slotResult, u"sl1:bubu"); + + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sl1, Qt::DirectConnection, qReturnArg(result), + u"byebye"_s)); + QCOMPARE(obj.slotResult, u"sl1:byebye"); + QCOMPARE(result, u"yessir"); + + QVERIFY(QMetaObject::invokeMethod(&obj, qOverload(&QtTestObject::overloadedSlot), 1, 2)); + QCOMPARE(obj.slotResult, u"overloadedSlot:1,2"); + + // non-const ref parameter + QString original = u"bubu"_s; + QString &ref = original; + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sl1, qReturnArg(result), ref)); + QCOMPARE(obj.slotResult, u"sl1:bubu"); + QCOMPARE(result, u"yessir"); + + struct R { + bool operator()(int) { return true; } + int operator()(char) { return 15; } + int operator()(QString = {}, int = {}, int = {}) { return 242; } + } r; + + // Test figuring out which operator() to call: + { + bool res = false; + QVERIFY(QMetaObject::invokeMethod(&obj, r, qReturnArg(res), 1)); + QCOMPARE(res, true); + } + { + int res; + QVERIFY(QMetaObject::invokeMethod(&obj, r, qReturnArg(res), 'c')); + QCOMPARE(res, 15); + } + { + int res; + QVERIFY(QMetaObject::invokeMethod(&obj, r, qReturnArg(res))); + QCOMPARE(res, 242); + res = 0; + QVERIFY(QMetaObject::invokeMethod(&obj, r, qReturnArg(res), u"bu"_s)); + QCOMPARE(res, 242); + res = 0; + QVERIFY(QMetaObject::invokeMethod(&obj, r, qReturnArg(res), u"bu"_s, 1)); + QCOMPARE(res, 242); + res = 0; + QVERIFY(QMetaObject::invokeMethod(&obj, r, qReturnArg(res), u"bu"_s, 1, 2)); + QCOMPARE(res, 242); + } + + { + auto lambda = [](const QString &s) { return s + s; }; + QVERIFY(QMetaObject::invokeMethod(&obj, lambda, qReturnArg(result), u"bu"_s)); + QCOMPARE(result, u"bubu"); + } + + { + auto lambda = [](const QString &s = u"bu"_s) { return s + s; }; + QVERIFY(QMetaObject::invokeMethod(&obj, lambda, qReturnArg(result))); + QCOMPARE(result, u"bubu"); + + QVERIFY(QMetaObject::invokeMethod(&obj, lambda, qReturnArg(result), u"bye"_s)); + QCOMPARE(result, u"byebye"); + } + + { + auto lambda = [](const QString &s, qint64 a, qint16 b, qint8 c) { + return s + QString::number(a) + QString::number(b) + QString::number(c); + }; + // Testing mismatching argument (int for qint64). The other arguments + // would static_assert for potential truncation if they were ints. + QVERIFY(QMetaObject::invokeMethod(&obj, lambda, qReturnArg(result), u"bu"_s, 1, qint16(2), qint8(3))); + QCOMPARE(result, u"bu123"); + } + { + // Testing deduction + auto lambda = [](const QString &s, auto a) { return s + a; }; + QVERIFY(QMetaObject::invokeMethod(&obj, lambda, qReturnArg(result), u"bu"_s, "bu"_L1)); + QCOMPARE(result, u"bubu"); + + auto variadic = [](const QString &s, auto... a) { return s + (QString::number(a) + ...); }; + QVERIFY(QMetaObject::invokeMethod(&obj, variadic, qReturnArg(result), u"bu"_s, 1, 2, 3, 4, 5, 6)); + QCOMPARE(result, u"bu123456"); + } } void tst_QMetaObject::invokeQueuedMetaMember() @@ -1345,6 +1439,12 @@ void tst_QMetaObject::invokeQueuedPointer() QCOMPARE(var, 0); } QCOMPARE(countedStructObjectsCount, 0); + + // Invoking with parameters + using namespace Qt::StringLiterals; + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sl1, Qt::QueuedConnection, u"bubu"_s)); + qApp->processEvents(QEventLoop::AllEvents); + QCOMPARE(obj.slotResult, u"sl1:bubu"); } // this test is duplicated below @@ -1764,6 +1864,18 @@ void tst_QMetaObject::invokeBlockingQueuedPointer() Qt::BlockingQueuedConnection)); QCOMPARE(obj.slotResult, QString("sl1:hehe")); } + + // Test with parameters + QString result; + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sl1, Qt::BlockingQueuedConnection, + qReturnArg(result), u"bubu"_s)); + QCOMPARE(result, u"yessir"); + QCOMPARE(obj.slotResult, u"sl1:bubu"); + + QVERIFY(QMetaObject::invokeMethod(&obj, &QtTestObject::sl2, Qt::BlockingQueuedConnection, + u"bubu"_s, u"baba"_s)); + QCOMPARE(obj.slotResult, u"sl2:bububaba"); + QVERIFY(QMetaObject::invokeMethod(&obj, [&](){obj.moveToThread(QThread::currentThread());}, Qt::BlockingQueuedConnection)); t.quit(); QVERIFY(t.wait());