diff --git a/src/corelib/kernel/qcoreapplication.h b/src/corelib/kernel/qcoreapplication.h index 82580ceb34..5a5ddf4837 100644 --- a/src/corelib/kernel/qcoreapplication.h +++ b/src/corelib/kernel/qcoreapplication.h @@ -115,67 +115,35 @@ public: Qt::PermissionStatus checkPermission(const QPermission &permission); # ifdef Q_QDOC - template - void requestPermission(const QPermission &permission, Functor functor); template void requestPermission(const QPermission &permission, const QObject *context, Functor functor); # else - template // requestPermission to a QObject slot + // requestPermission with context or receiver object; need to require here that receiver is the + // right type to avoid ambiguity with the private implementation function. + template void requestPermission(const QPermission &permission, - const typename QtPrivate::FunctionPointer::Object *receiver, Slot slot) + const typename QtPrivate::ContextTypeForFunctor::ContextType *receiver, + Functor func) { - using CallbackSignature = QtPrivate::FunctionPointer; - using SlotSignature = QtPrivate::FunctionPointer; - - static_assert(int(SlotSignature::ArgumentCount) <= int(CallbackSignature::ArgumentCount), - "Slot requires more arguments than what can be provided."); - static_assert((QtPrivate::CheckCompatibleArguments::value), - "Slot arguments are not compatible (must be QPermission)"); - - auto slotObj = new QtPrivate::QSlotObject(slot); - requestPermission(permission, slotObj, receiver); - } - - // requestPermission to a functor or function pointer (with context) - template ::IsPointerToMemberFunction - && !std::is_same::value, bool> = true> - void requestPermission(const QPermission &permission, const QObject *context, Func func) - { - using CallbackSignature = QtPrivate::FunctionPointer; - constexpr int MatchingArgumentCount = QtPrivate::ComputeFunctorArgumentCount< - Func, CallbackSignature::Arguments>::Value; - - static_assert(MatchingArgumentCount == 0 - || MatchingArgumentCount == CallbackSignature::ArgumentCount, - "Functor arguments are not compatible (must be QPermission)"); - - QtPrivate::QSlotObjectBase *slotObj = nullptr; - if constexpr (MatchingArgumentCount == CallbackSignature::ArgumentCount) { - slotObj = new QtPrivate::QFunctorSlotObject(std::move(func)); - } else { - slotObj = new QtPrivate::QFunctorSlotObject::Value, void>(std::move(func)); - } - - requestPermission(permission, slotObj, context); + using Prototype = void(*)(QPermission); + requestPermission(permission, + QtPrivate::makeSlotObject(std::move(func)), + receiver); } +# endif // Q_QDOC // requestPermission to a functor or function pointer (without context) - template ::IsPointerToMemberFunction - && !std::is_same::value, bool> = true> - void requestPermission(const QPermission &permission, Func func) + template + void requestPermission(const QPermission &permission, Functor func) { requestPermission(permission, nullptr, std::move(func)); } private: + // ### Qt 7: rename to requestPermissionImpl to avoid ambiguity void requestPermission(const QPermission &permission, QtPrivate::QSlotObjectBase *slotObj, const QObject *context); public: -# endif // Q_QDOC #endif // QT_CONFIG(permission) diff --git a/src/corelib/kernel/qobjectdefs_impl.h b/src/corelib/kernel/qobjectdefs_impl.h index 10f06609ec..fea9e0ac52 100644 --- a/src/corelib/kernel/qobjectdefs_impl.h +++ b/src/corelib/kernel/qobjectdefs_impl.h @@ -333,6 +333,27 @@ namespace QtPrivate { typedef decltype(dummy().operator()((dummy())...)) Value; }; + /* + Wrapper around ComputeFunctorArgumentCount and CheckCompatibleArgument, + depending on whether \a Functor is a PMF or not. Returns -1 if \a Func is + not compatible with the \a ExpectedArguments, otherwise returns >= 0. + */ + template + constexpr int inline countMatchingArguments() + { + using ExpectedArguments = typename QtPrivate::FunctionPointer::Arguments; + + if constexpr (QtPrivate::FunctionPointer::IsPointerToMemberFunction) { + using ActualArguments = typename QtPrivate::FunctionPointer::Arguments; + if constexpr (QtPrivate::CheckCompatibleArguments::value) + return QtPrivate::FunctionPointer::ArgumentCount; + else + return -1; + } else { + return QtPrivate::ComputeFunctorArgumentCount::Value; + } + } + // internal base class (interface) containing functions required to call a slot managed by a pointer to function. class QSlotObjectBase { QAtomicInt m_ref; @@ -428,6 +449,64 @@ namespace QtPrivate { template using QFunctorSlotObjectWithNoArgsImplicitReturn = QFunctorSlotObjectWithNoArgs::ReturnType>; + + + // Helper to detect the context object type based on the functor type: + // QObject for free functions and lambdas; the callee for member function + // pointers. The default declaration doesn't have the ContextType typedef, + // and so non-functor APIs (like old-style string-based slots) are removed + // from the overload set. + template + struct ContextTypeForFunctor {}; + + template + struct ContextTypeForFunctor, + std::is_member_function_pointer>> + > + > + { + using ContextType = QObject; + }; + template + struct ContextTypeForFunctor>, + std::is_member_function_pointer, + std::is_convertible::Object *, QObject *>> + > + > + { + using ContextType = typename QtPrivate::FunctionPointer::Object; + }; + + /* + Returns a suitable QSlotObjectBase object that holds \a func, if possible. + + Not available (and thus produces compile-time errors) if the Functor provided is + not compatible with the expected Prototype. + */ + template + static constexpr std::enable_if_t() >= 0, + QtPrivate::QSlotObjectBase *> + makeSlotObject(Functor &&func) + { + using ExpectedSignature = QtPrivate::FunctionPointer; + using ExpectedArguments = typename ExpectedSignature::Arguments; + using ActualSignature = QtPrivate::FunctionPointer; + + static_assert(int(ActualSignature::ArgumentCount) <= int(ExpectedSignature::ArgumentCount), + "Functor requires more arguments than what can be provided."); + constexpr int MatchingArgumentCount = QtPrivate::countMatchingArguments(); + + if constexpr (QtPrivate::FunctionPointer::IsPointerToMemberFunction) { + using ActualArguments = typename ActualSignature::Arguments; + return new QtPrivate::QSlotObject(func); + } else { + using ActualArguments = typename QtPrivate::List_Left::Value; + + return new QtPrivate::QFunctorSlotObject(std::move(func)); + } + } } QT_END_NAMESPACE diff --git a/src/network/kernel/qhostinfo.h b/src/network/kernel/qhostinfo.h index 47db075b44..d990af685a 100644 --- a/src/network/kernel/qhostinfo.h +++ b/src/network/kernel/qhostinfo.h @@ -56,61 +56,29 @@ public: static QString localDomainName(); #ifdef Q_QDOC - template - static int lookupHost(const QString &name, Functor functor); template static int lookupHost(const QString &name, const QObject *context, Functor functor); #else - // lookupHost to a QObject slot - template + // lookupHost to a callable (with context) + template static inline int lookupHost(const QString &name, - const typename QtPrivate::FunctionPointer::Object *receiver, - Func slot) + const typename QtPrivate::ContextTypeForFunctor::ContextType *receiver, + Functor func) { - typedef QtPrivate::FunctionPointer SlotType; - - typedef QtPrivate::FunctionPointer SignalType; - static_assert(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount), - "The slot requires more arguments than the signal provides."); - static_assert((QtPrivate::CheckCompatibleArguments::value), - "Signal and slot arguments are not compatible."); - static_assert((QtPrivate::AreArgumentsCompatible::value), - "Return type of the slot is not compatible " - "with the return type of the signal."); - - auto slotObj = new QtPrivate::QSlotObject(slot); - return lookupHostImpl(name, receiver, slotObj, nullptr); + using Prototype = void(*)(QHostInfo); + return lookupHostImpl(name, receiver, + QtPrivate::makeSlotObject(std::move(func)), + nullptr); } +#endif // Q_QDOC // lookupHost to a callable (without context) - template - static inline typename std::enable_if::IsPointerToMemberFunction && - !std::is_same::value, int>::type - lookupHost(const QString &name, Func slot) + template + static inline int lookupHost(const QString &name, Functor slot) { return lookupHost(name, nullptr, std::move(slot)); } - // lookupHost to a functor or function pointer (with context) - template - static inline typename std::enable_if::IsPointerToMemberFunction && - !std::is_same::value, int>::type - lookupHost(const QString &name, QObject *context, Func1 slot) - { - typedef QtPrivate::FunctionPointer SlotType; - - static_assert(int(SlotType::ArgumentCount) <= 1, - "The slot must not require more than one argument"); - - auto slotObj = new QtPrivate::QFunctorSlotObject, - void>(std::move(slot)); - return lookupHostImpl(name, context, slotObj, nullptr); - } -#endif // Q_QDOC - private: QHostInfoPrivate *d_ptr; Q_DECLARE_PRIVATE(QHostInfo) diff --git a/tests/auto/corelib/kernel/qpermission/tst_qpermission.cpp b/tests/auto/corelib/kernel/qpermission/tst_qpermission.cpp index b698969652..146c1b2660 100644 --- a/tests/auto/corelib/kernel/qpermission/tst_qpermission.cpp +++ b/tests/auto/corelib/kernel/qpermission/tst_qpermission.cpp @@ -26,6 +26,7 @@ private Q_SLOTS: void conversionMaintainsState() const; + void functorWithoutContext(); void functorWithContextInThread(); void receiverInThread(); void destroyedContextObject(); @@ -145,6 +146,23 @@ void tst_QPermission::conversionMaintainsState() const } } +// Compile test for context-less functor overloads +void tst_QPermission::functorWithoutContext() +{ + int argc = 0; + char *argv = nullptr; + QCoreApplication app(argc, &argv); + + DummyPermission dummy; +#ifdef Q_OS_DARWIN + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Could not find permission plugin for DummyPermission.*")); +#endif + + qApp->requestPermission(dummy, [](const QPermission &permission){ + QVERIFY(permission.value()); + }); +} + void tst_QPermission::functorWithContextInThread() { int argc = 0; @@ -209,6 +227,11 @@ void tst_QPermission::receiverInThread() qApp->requestPermission(dummy, &receiver, &Receiver::handlePermission); QTRY_COMPARE(receiver.permissionReceiverThread, &receiverThread); + + // compile tests: none of these work and the error output isn't horrible + // qApp->requestPermission(dummy, &receiver, "&tst_QPermission::receiverInThread"); + // qApp->requestPermission(dummy, &receiver, &tst_QPermission::receiverInThread); + // qApp->requestPermission(dummy, &receiver, &QObject::destroyed); } void tst_QPermission::destroyedContextObject()