Support free functions and const functors as callbacks

Amend 207aae5560, as code checker
complained that we std::move'd a potential lvalue. This warning was
valid if the public API did not accept the functor parameter by value.

Fix this by consistently std::forward'ing the parameters through the
call stack, and add a compile-time test. Writing that test revealed that
the helper API didn't work with free functions, so fix that as well. It
also revealed that QFunctorSlotObject couldn't work with a const
functor, which is also fixed by this change.

We cannot support move-only functors with that change, as it requires
a change to QFunctorSlotObject that breaks the QMetaObject test.

Change-Id: Iafd747baf4cb0213ecedb391ed46b4595388182b
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Volker Hilsheimer 2023-04-27 14:53:40 +02:00
parent 10a850f584
commit bd69821074
4 changed files with 169 additions and 16 deletions

View File

@ -123,20 +123,20 @@ public:
template <typename Functor>
void requestPermission(const QPermission &permission,
const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *receiver,
Functor func)
Functor &&func)
{
using Prototype = void(*)(QPermission);
requestPermission(permission,
QtPrivate::makeSlotObject<Prototype>(std::move(func)),
QtPrivate::makeSlotObject<Prototype>(std::forward<Functor>(func)),
receiver);
}
# endif // Q_QDOC
// requestPermission to a functor or function pointer (without context)
template <typename Functor>
void requestPermission(const QPermission &permission, Functor func)
void requestPermission(const QPermission &permission, Functor &&func)
{
requestPermission(permission, nullptr, std::move(func));
requestPermission(permission, nullptr, std::forward<Functor>(func));
}
private:

View File

@ -342,15 +342,19 @@ namespace QtPrivate {
constexpr int inline countMatchingArguments()
{
using ExpectedArguments = typename QtPrivate::FunctionPointer<Prototype>::Arguments;
using Actual = std::decay_t<Functor>;
if constexpr (QtPrivate::FunctionPointer<Functor>::IsPointerToMemberFunction) {
using ActualArguments = typename QtPrivate::FunctionPointer<Functor>::Arguments;
if constexpr (QtPrivate::FunctionPointer<Actual>::IsPointerToMemberFunction
|| QtPrivate::FunctionPointer<Actual>::ArgumentCount >= 0) {
// PMF or free function
using ActualArguments = typename QtPrivate::FunctionPointer<Actual>::Arguments;
if constexpr (QtPrivate::CheckCompatibleArguments<ExpectedArguments, ActualArguments>::value)
return QtPrivate::FunctionPointer<Functor>::ArgumentCount;
return QtPrivate::FunctionPointer<Actual>::ArgumentCount;
else
return -1;
} else {
return QtPrivate::ComputeFunctorArgumentCount<Functor, ExpectedArguments>::Value;
// lambda or functor
return QtPrivate::ComputeFunctorArgumentCount<Actual, ExpectedArguments>::Value;
}
}
@ -488,7 +492,7 @@ namespace QtPrivate {
template <typename Prototype, typename Functor>
static constexpr std::enable_if_t<QtPrivate::countMatchingArguments<Prototype, Functor>() >= 0,
QtPrivate::QSlotObjectBase *>
makeSlotObject(Functor &&func)
makeSlotObject(Functor func)
{
using ExpectedSignature = QtPrivate::FunctionPointer<Prototype>;
using ExpectedArguments = typename ExpectedSignature::Arguments;
@ -496,15 +500,16 @@ namespace QtPrivate {
static_assert(int(ActualSignature::ArgumentCount) <= int(ExpectedSignature::ArgumentCount),
"Functor requires more arguments than what can be provided.");
constexpr int MatchingArgumentCount = QtPrivate::countMatchingArguments<Prototype, Functor>();
if constexpr (QtPrivate::FunctionPointer<Functor>::IsPointerToMemberFunction) {
using ActualArguments = typename ActualSignature::Arguments;
return new QtPrivate::QSlotObject<Functor, ActualArguments, void>(func);
return new QtPrivate::QSlotObject<Functor, ActualArguments, void>(func);
} else {
constexpr int MatchingArgumentCount = QtPrivate::countMatchingArguments<Prototype, Functor>();
using ActualArguments = typename QtPrivate::List_Left<ExpectedArguments, MatchingArgumentCount>::Value;
return new QtPrivate::QFunctorSlotObject<Functor, MatchingArgumentCount, ActualArguments, void>(std::move(func));
return new QtPrivate::QFunctorSlotObject<Functor, MatchingArgumentCount,
ActualArguments, void>(std::move(func));
}
}
}

View File

@ -63,20 +63,20 @@ public:
template <typename Functor>
static inline int lookupHost(const QString &name,
const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *receiver,
Functor func)
Functor &&func)
{
using Prototype = void(*)(QHostInfo);
return lookupHostImpl(name, receiver,
QtPrivate::makeSlotObject<Prototype>(std::move(func)),
QtPrivate::makeSlotObject<Prototype>(std::forward<Functor>(func)),
nullptr);
}
#endif // Q_QDOC
// lookupHost to a callable (without context)
template <typename Functor>
static inline int lookupHost(const QString &name, Functor slot)
static inline int lookupHost(const QString &name, Functor &&slot)
{
return lookupHost(name, nullptr, std::move(slot));
return lookupHost(name, nullptr, std::forward<Functor>(slot));
}
private:

View File

@ -149,6 +149,7 @@ private slots:
void objectNameBinding();
void emitToDestroyedClass();
void declarativeData();
void asyncCallbackHelper();
};
struct QObjectCreatedOnShutdown
@ -8355,5 +8356,152 @@ void tst_QObject::declarativeData()
#endif
}
/*
Compile-time test for the helpers in qobjectdefs_impl.h.
*/
class AsyncCaller : public QObject
{
Q_OBJECT
public:
void callback0() {}
void callback1(const QString &) {}
void callbackInt(int) {}
int returnInt() const { return 0; }
static void staticCallback0() {}
static void staticCallback1(const QString &) {}
using Prototype0 = void(*)();
using Prototype1 = void(*)(QString);
template<typename Functor>
bool callMe0(const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *, Functor &&func)
{
auto *slotObject = QtPrivate::makeSlotObject<Prototype0>(std::forward<Functor>(func));
slotObject->destroyIfLastRef();
return true;
}
template<typename Functor>
bool callMe0(Functor &&func)
{
return callMe0(nullptr, std::forward<Functor>(func));
}
template<typename Functor>
bool callMe1(const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *, Functor &&func)
{
auto *slotObject = QtPrivate::makeSlotObject<Prototype1>(std::forward<Functor>(func));
slotObject->destroyIfLastRef();
return true;
}
template<typename Functor>
bool callMe1(Functor &&func)
{
return callMe1(nullptr, std::forward<Functor>(func));
}
};
static void freeFunction0() {}
static void freeFunction1(QString) {}
template<typename Prototype, typename Functor, typename = void>
struct AreFunctionsCompatible : std::false_type {};
template<typename Prototype, typename Functor>
struct AreFunctionsCompatible<Prototype, Functor, std::enable_if_t<
std::is_same_v<decltype(QtPrivate::makeSlotObject<Prototype>(std::forward<Functor>(std::declval<Functor>()))),
QtPrivate::QSlotObjectBase *>>
> : std::true_type {};
template<typename Prototype, typename Functor>
inline constexpr bool compiles(Functor &&) {
return QtPrivate::AreFunctionsCompatible<Prototype, Functor>::value;
}
void tst_QObject::asyncCallbackHelper()
{
auto lambda0 = []{};
auto lambda1 = [](const QString &) {};
auto lambda2 = [](const QString &, int) {};
const auto constLambda = [](const QString &) {};
auto moveOnlyLambda = [u = std::unique_ptr<int>()]{};
SlotFunctor functor0;
SlotFunctorString functor1;
// no parameters provided or needed
static_assert(compiles<AsyncCaller::Prototype0>(&AsyncCaller::callback0));
static_assert(compiles<AsyncCaller::Prototype0>(&AsyncCaller::staticCallback0));
static_assert(compiles<AsyncCaller::Prototype0>(lambda0));
static_assert(compiles<AsyncCaller::Prototype0>(freeFunction0));
static_assert(compiles<AsyncCaller::Prototype0>(functor0));
// more parameters than needed
static_assert(compiles<AsyncCaller::Prototype1>(&AsyncCaller::callback0));
static_assert(compiles<AsyncCaller::Prototype1>(&AsyncCaller::staticCallback0));
static_assert(compiles<AsyncCaller::Prototype1>(lambda0));
static_assert(compiles<AsyncCaller::Prototype1>(freeFunction0));
static_assert(compiles<AsyncCaller::Prototype1>(functor0));
// matching parameter
static_assert(compiles<AsyncCaller::Prototype1>(&AsyncCaller::callback1));
static_assert(compiles<AsyncCaller::Prototype1>(&AsyncCaller::staticCallback1));
static_assert(compiles<AsyncCaller::Prototype1>(lambda1));
static_assert(compiles<AsyncCaller::Prototype1>(constLambda));
static_assert(compiles<AsyncCaller::Prototype1>(freeFunction1));
static_assert(compiles<AsyncCaller::Prototype1>(functor1));
// not enough parameters
static_assert(!compiles<AsyncCaller::Prototype0>(&AsyncCaller::callback1));
static_assert(!compiles<AsyncCaller::Prototype0>(&AsyncCaller::staticCallback1));
static_assert(!compiles<AsyncCaller::Prototype0>(lambda1));
static_assert(!compiles<AsyncCaller::Prototype0>(constLambda));
static_assert(!compiles<AsyncCaller::Prototype0>(lambda2));
static_assert(!compiles<AsyncCaller::Prototype0>(freeFunction1));
static_assert(!compiles<AsyncCaller::Prototype0>(functor1));
// move-only functor - should work, but doesn't because QFunctorSlotObject requires
// the functor to be of a copyable type!
static_assert(!compiles<AsyncCaller::Prototype0>(moveOnlyLambda));
static_assert(!compiles<AsyncCaller::Prototype1>(moveOnlyLambda));
// wrong parameter type
static_assert(!compiles<AsyncCaller::Prototype1>(&AsyncCaller::callbackInt));
// old-style slot name
static_assert(!compiles<AsyncCaller::Prototype0>("callback1"));
// slot with return value is ok, we just don't pass
// the return value through to anything.
static_assert(compiles<AsyncCaller::Prototype0>(&AsyncCaller::returnInt));
AsyncCaller caller;
// with context
QVERIFY(caller.callMe0(&caller, &AsyncCaller::callback0));
QVERIFY(caller.callMe0(&caller, &AsyncCaller::returnInt));
QVERIFY(caller.callMe0(&caller, &AsyncCaller::staticCallback0));
QVERIFY(caller.callMe0(&caller, lambda0));
QVERIFY(caller.callMe0(&caller, freeFunction0));
// QVERIFY(caller.callMe0(&caller, moveOnlyLambda));
QVERIFY(caller.callMe1(&caller, &AsyncCaller::callback1));
QVERIFY(caller.callMe1(&caller, &AsyncCaller::staticCallback1));
QVERIFY(caller.callMe1(&caller, lambda1));
QVERIFY(caller.callMe1(&caller, freeFunction1));
QVERIFY(caller.callMe1(&caller, constLambda));
// without context
QVERIFY(caller.callMe0(&AsyncCaller::staticCallback0));
QVERIFY(caller.callMe0(lambda0));
QVERIFY(caller.callMe0(freeFunction0));
// QVERIFY(caller.callMe0(moveOnlyLambda));
QVERIFY(caller.callMe1(&AsyncCaller::staticCallback1));
QVERIFY(caller.callMe1(lambda1));
QVERIFY(caller.callMe1(constLambda));
QVERIFY(caller.callMe1(freeFunction1));
}
QTEST_MAIN(tst_QObject)
#include "tst_qobject.moc"