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 <ievgenii.meshcheriakov@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Mårten Nordheim 2023-06-27 18:29:38 +02:00
parent 777a1ed191
commit a7d2855b3c
6 changed files with 318 additions and 28 deletions

View File

@ -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)

View File

@ -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<void **>(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<QMetaCallEvent>(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<typename Functor, typename FunctorReturnType, typename... Args> bool QMetaObject::invokeMethod(QObject *context, Functor &&function, Qt::ConnectionType type, QTemplatedMetaMethodReturnArgument<FunctorReturnType> ret, Args &&...arguments)
\fn template<typename Functor, typename FunctorReturnType, typename... Args> bool QMetaObject::invokeMethod(QObject *context, Functor &&function, QTemplatedMetaMethodReturnArgument<FunctorReturnType> ret, Args &&...arguments)
\fn template<typename Functor, typename... Args> bool QMetaObject::invokeMethod(QObject *context, Functor &&function, Qt::ConnectionType type, Args &&...arguments)
\fn template<typename Functor, typename... Args> 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)

View File

@ -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<typename Functor, typename FunctorReturnType>
static bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret);
template<typename Functor, typename FunctorReturnType, typename... Args>
static bool invokeMethod(QObject *context, Functor &&function, Qt::ConnectionType type, QTemplatedMetaMethodReturnArgument<FunctorReturnType> ret, Args &&...arguments);
template<typename Functor, typename FunctorReturnType, typename... Args>
static bool invokeMethod(QObject *context, Functor &&function, QTemplatedMetaMethodReturnArgument<FunctorReturnType> ret, Args &&...arguments);
template<typename Functor, typename... Args>
static bool invokeMethod(QObject *context, Functor &&function, Qt::ConnectionType type, Args &&...arguments);
template<typename Functor, typename... Args>
static bool invokeMethod(QObject *context, Functor &&function, Args &&...arguments);
#else
template <typename Func>
static std::enable_if_t<!std::disjunction_v<std::is_convertible<Func, const char *>,
QtPrivate::Invoke::AreOldStyleArgs<Func>>,
bool>
invokeMethod(typename QtPrivate::ContextTypeForFunctor<Func>::ContextType *object,
Func &&function,
Qt::ConnectionType type = Qt::AutoConnection,
typename QtPrivate::Callable<Func>::ReturnType *ret = nullptr)
Func &&function, Qt::ConnectionType type,
typename QtPrivate::Callable<Func>::ReturnType *ret)
{
static_assert(QtPrivate::Callable<Func>::ArgumentCount <= 0,
"QMetaObject::invokeMethod cannot call functions with arguments!");
using Prototype = typename QtPrivate::Callable<Func>::Function;
return invokeMethodImpl(object, QtPrivate::makeCallableObject<Prototype>(std::forward<Func>(function)), type, ret);
using R = typename QtPrivate::Callable<Func>::ReturnType;
const auto getReturnArg = [ret]() -> QTemplatedMetaMethodReturnArgument<R> {
if constexpr (std::is_void_v<R>)
return {};
else
return ret ? qReturnArg(*ret) : QTemplatedMetaMethodReturnArgument<R>{};
};
return invokeMethod(object, std::forward<Func>(function), type, getReturnArg());
}
template <typename Func>
static std::enable_if_t<!std::disjunction_v<std::is_convertible<Func, const char *>,
QtPrivate::Invoke::AreOldStyleArgs<Func>>,
bool>
invokeMethod(typename QtPrivate::ContextTypeForFunctor<Func>::ContextType *object,
Func &&function,
typename QtPrivate::Callable<Func>::ReturnType *ret)
Func &&function, typename QtPrivate::Callable<Func>::ReturnType *ret)
{
return invokeMethod(object, std::forward<Func>(function), Qt::AutoConnection, ret);
}
template <typename Func, typename... Args>
static std::enable_if_t<!std::disjunction_v<std::is_convertible<Func, const char *>,
QtPrivate::Invoke::AreOldStyleArgs<Args...>>,
bool>
invokeMethod(typename QtPrivate::ContextTypeForFunctor<Func>::ContextType *object,
Func &&function, Qt::ConnectionType type,
QTemplatedMetaMethodReturnArgument<
typename QtPrivate::Callable<Func, Args...>::ReturnType>
ret,
Args &&...args)
{
return invokeMethodCallableHelper(object, std::forward<Func>(function), type, ret,
std::forward<Args>(args)...);
}
template <typename Func, typename... Args>
static std::enable_if_t<!std::disjunction_v<std::is_convertible<Func, const char *>,
QtPrivate::Invoke::AreOldStyleArgs<Args...>>,
bool>
invokeMethod(typename QtPrivate::ContextTypeForFunctor<Func>::ContextType *object,
Func &&function, Qt::ConnectionType type, Args &&...args)
{
using R = typename QtPrivate::Callable<Func, Args...>::ReturnType;
QTemplatedMetaMethodReturnArgument<R> r{ QtPrivate::qMetaTypeInterfaceForType<R>(), nullptr,
nullptr };
return invokeMethod(object, std::forward<Func>(function), type, r,
std::forward<Args>(args)...);
}
template <typename Func, typename... Args>
static std::enable_if_t<!std::disjunction_v<std::is_convertible<Func, const char *>,
QtPrivate::Invoke::AreOldStyleArgs<Args...>>,
bool>
invokeMethod(typename QtPrivate::ContextTypeForFunctor<Func>::ContextType *object,
Func &&function,
QTemplatedMetaMethodReturnArgument<
typename QtPrivate::Callable<Func, Args...>::ReturnType>
ret,
Args &&...args)
{
return invokeMethod(object, std::forward<Func>(function), Qt::AutoConnection, ret,
std::forward<Args>(args)...);
}
template <typename Func, typename... Args>
static std::enable_if_t<!std::disjunction_v<std::is_convertible<Func, const char *>,
QtPrivate::Invoke::AreOldStyleArgs<Args...>>,
bool>
invokeMethod(typename QtPrivate::ContextTypeForFunctor<Func>::ContextType *object,
Func &&function, Args &&...args)
{
using R = typename QtPrivate::Callable<Func, Args...>::ReturnType;
QTemplatedMetaMethodReturnArgument<R> r{ QtPrivate::qMetaTypeInterfaceForType<R>(), nullptr,
nullptr };
return invokeMethod(object, std::forward<Func>(function), Qt::AutoConnection, r,
std::forward<Args>(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 <typename Func, typename... Args>
static bool
invokeMethodCallableHelper(typename QtPrivate::ContextTypeForFunctor<Func>::ContextType *object,
Func &&function, Qt::ConnectionType type, const QMetaMethodReturnArgument &ret,
Args &&...args)
{
using Callable = QtPrivate::Callable<Func, Args...>;
using ExpectedArguments = typename Callable::Arguments;
static_assert(sizeof...(Args) <= ExpectedArguments::size, "Too many arguments");
using ActualArguments = QtPrivate::List<Args...>;
static_assert(QtPrivate::CheckCompatibleArguments<ActualArguments,
ExpectedArguments>::value,
"Incompatible arguments");
auto h = QtPrivate::invokeMethodHelper(ret, args...);
auto callable = new QtPrivate::QCallableObject<std::decay_t<Func>, ActualArguments,
typename Callable::ReturnType>(std::forward<Func>(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);

View File

@ -340,20 +340,55 @@ namespace QtPrivate {
}
};
template<typename Func>
struct ZeroArgFunctor : Functor<Func, 0>
template<typename Func, typename... Args>
struct FunctorCallable : Functor<Func, sizeof...(Args)>
{
using ReturnType = decltype(std::declval<Func>()());
using Function = ReturnType(*)();
enum {ArgumentCount = 0};
using Arguments = QtPrivate::List<>;
using ReturnType = decltype(std::declval<Func>()(std::declval<Args>()...));
using Function = ReturnType(*)(Args...);
enum {ArgumentCount = sizeof...(Args)};
using Arguments = QtPrivate::List<Args...>;
};
template<typename Func>
using Callable = std::conditional_t<FunctionPointer<std::decay_t<Func>>::ArgumentCount == -1,
ZeroArgFunctor<std::decay_t<Func>>,
FunctionPointer<std::decay_t<Func>>
>;
template <typename Functor, typename... Args>
struct HasCallOperatorAcceptingArgs
{
private:
template <typename F, typename = void>
struct Test : std::false_type
{
};
// We explicitly use .operator() to not return true for pointers to free/static function
template <typename F>
struct Test<F, std::void_t<decltype(std::declval<F>().operator()(std::declval<Args>()...))>>
: std::true_type
{
};
public:
using Type = Test<Functor>;
static constexpr bool value = Type::value;
};
template <typename Functor, typename... Args>
constexpr bool
HasCallOperatorAcceptingArgs_v = HasCallOperatorAcceptingArgs<Functor, Args...>::value;
template <typename Func, typename... Args>
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<Func, Args...>;
static auto Resolve(std::false_type CallOperator) -> FunctionPointer<std::decay_t<Func>>;
public:
using Type = decltype(Resolve(typename HasCallOperatorAcceptingArgs<std::decay_t<Func>,
Args...>::Type{}));
};
template<typename Func, typename... Args>
using Callable = typename CallableHelper<Func, Args...>::Type;
/*
Wrapper around ComputeFunctorArgumentCount and CheckCompatibleArgument,

View File

@ -366,8 +366,10 @@ void QTimer::singleShotImpl(int msec, Qt::TimerType timerType,
deleteReceiver = true;
}
auto h = QtPrivate::invokeMethodHelper({});
QMetaObject::invokeMethodImpl(const_cast<QObject *>(receiver), slotObj,
Qt::QueuedConnection, nullptr);
Qt::QueuedConnection, h.parameterCount(), h.parameters.data(), h.typeNames.data(),
h.metaTypes.data());
if (deleteReceiver)
const_cast<QObject *>(receiver)->deleteLater();

View File

@ -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<int, int>(&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());