diff options
author | Volker Hilsheimer <volker.hilsheimer@qt.io> | 2023-05-02 17:39:34 +0200 |
---|---|---|
committer | Volker Hilsheimer <volker.hilsheimer@qt.io> | 2023-05-05 21:45:56 +0200 |
commit | 3bf5b5f8944dd417530b09dd6f1cd568717c8cc1 (patch) | |
tree | e6193d7444330b00bacb60c4d3bfafb6b9f2e96b | |
parent | 2162e0dfc4b714d24c5610cab1e37e0e0cf4c74e (diff) |
Use QSlotObject helpers in functor-cases of QMetaObject::invoke
Add helper that allows us to determine the argument list and return type
of a functor. This triggers a compile time error if the functor has
operator()() overloads (we only support zero-argument call operators, but
there might be const/noexcept variations). Use that helper to declare a
ZeroArgFunctor type which also declares a ReturnType and Arguments alias.
Add a Callable alias that now combines FunctionPointer and ZeroArgFunctor
into a single type that we can then use to merge the specializations of
QMetaObject::invokeMethod.
[ChangeLog][Potentially source-incompatible changes] Using a functor
with several operator() overloads in QMetaObject::invokeMethod now causes
a compile time error. Qt would previously ignore const and noexcept
overloads and always call the mutable version on a copy of the functor.
Change-Id: I3eb62c1128014b729575540deab615469290daeb
Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
-rw-r--r-- | src/corelib/kernel/qobjectdefs.h | 43 | ||||
-rw-r--r-- | src/corelib/kernel/qobjectdefs_impl.h | 58 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qobject/tst_qobject.cpp | 24 |
3 files changed, 71 insertions, 54 deletions
diff --git a/src/corelib/kernel/qobjectdefs.h b/src/corelib/kernel/qobjectdefs.h index 88b5437683..c79e02437f 100644 --- a/src/corelib/kernel/qobjectdefs.h +++ b/src/corelib/kernel/qobjectdefs.h @@ -410,53 +410,30 @@ struct Q_CORE_EXPORT QMetaObject template<typename Functor, typename FunctorReturnType> static bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret); #else - - // invokeMethod() for member function pointer or function pointer template <typename Func> - static typename std::enable_if<QtPrivate::FunctionPointer<Func>::ArgumentCount == 0, bool>::type + 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::FunctionPointer<Func>::ReturnType *ret = nullptr) + typename QtPrivate::Callable<Func>::ReturnType *ret = nullptr) { - return invokeMethodImpl(object, QtPrivate::makeSlotObject<Func>(std::forward<Func>(function)), type, ret); + using Prototype = typename QtPrivate::Callable<Func>::Function; + return invokeMethodImpl(object, QtPrivate::makeSlotObject<Prototype>(std::forward<Func>(function)), type, ret); } template <typename Func> - static typename std::enable_if<QtPrivate::FunctionPointer<Func>::ArgumentCount == 0, bool>::type + 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::FunctionPointer<Func>::ReturnType *ret) + typename QtPrivate::Callable<Func>::ReturnType *ret) { return invokeMethod(object, std::forward<Func>(function), Qt::AutoConnection, ret); } - // invokeMethod() for Functor - template <typename Func> - static typename std::enable_if<!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction - && QtPrivate::FunctionPointer<Func>::ArgumentCount == -1 - && !std::is_convertible<Func, const char*>::value, bool>::type - invokeMethod(QObject *context, Func function, - Qt::ConnectionType type = Qt::AutoConnection, decltype(function()) *ret = nullptr) - { - return invokeMethodImpl(context, - new QtPrivate::QFunctorSlotObjectWithNoArgs<Func, decltype(function())>(std::move(function)), - type, - ret); - } - - template <typename Func> - static typename std::enable_if<!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction - && QtPrivate::FunctionPointer<Func>::ArgumentCount == -1 - && !std::is_convertible<Func, const char*>::value, bool>::type - invokeMethod(QObject *context, Func function, decltype(function()) *ret) - { - return invokeMethodImpl(context, - new QtPrivate::QFunctorSlotObjectWithNoArgs<Func, decltype(function())>(std::move(function)), - Qt::AutoConnection, - ret); - } - #endif #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) diff --git a/src/corelib/kernel/qobjectdefs_impl.h b/src/corelib/kernel/qobjectdefs_impl.h index 156a6d6c42..f87b928263 100644 --- a/src/corelib/kernel/qobjectdefs_impl.h +++ b/src/corelib/kernel/qobjectdefs_impl.h @@ -239,14 +239,6 @@ namespace QtPrivate { } }; - template<typename Function, int N> struct Functor - { - template <typename SignalArgs, typename R> - static void call(Function &f, void *, void **arg) { - FunctorCall<typename Indexes<N>::Value, SignalArgs, R, Function>::call(f, arg); - } - }; - // Traits to detect if there is a conversion between two types, // and that conversion does not include a narrowing conversion. template <typename T> @@ -335,6 +327,43 @@ namespace QtPrivate { typedef decltype(std::declval<Functor>().operator()((std::declval<ArgList>())...)) Value; }; + // Get the function prototype for a functor. There can only be one call operator + // in a functor, otherwise we get errors from ambiguity. But that's good enough. + template <typename Ret, typename... Args> + using FunctionTypeForTypes = Ret(*)(Args...); + + template <typename Ret, typename Obj, typename... Args> + FunctionTypeForTypes<Ret, Args...> FunctorPrototype(Ret(Obj::*)(Args...) const) { return nullptr; } + template <typename Ret, typename Obj, typename... Args> + FunctionTypeForTypes<Ret, Args...> FunctorPrototype(Ret(Obj::*)(Args...)) { return nullptr; } + template <typename Ret, typename Obj, typename... Args> + FunctionTypeForTypes<Ret, Args...> FunctorPrototype(Ret(Obj::*)(Args...) const noexcept) { return nullptr; } + template <typename Ret, typename Obj, typename... Args> + FunctionTypeForTypes<Ret, Args...> FunctorPrototype(Ret(Obj::*)(Args...) noexcept) { return nullptr; } + + template<typename Function, int N> struct Functor + { + template <typename SignalArgs, typename R> + static void call(Function &f, void *, void **arg) { + FunctorCall<typename Indexes<N>::Value, SignalArgs, R, Function>::call(f, arg); + } + }; + + template<typename Func> + struct ZeroArgFunctor : Functor<Func, 0> + { + using Function = decltype(FunctorPrototype(&std::decay_t<Func>::operator())); + enum {ArgumentCount = 0}; + using Arguments = QtPrivate::List<>; + using ReturnType = typename FunctionPointer<Function>::ReturnType; + }; + + template<typename Func> + using Callable = std::conditional_t<FunctionPointer<Func>::ArgumentCount == -1, + ZeroArgFunctor<Func>, + FunctionPointer<Func> + >; + /* Wrapper around ComputeFunctorArgumentCount and CheckCompatibleArgument, depending on whether \a Functor is a PMF or not. Returns -1 if \a Func is @@ -440,19 +469,6 @@ namespace QtPrivate { explicit QFunctorSlotObject(const Func &f) : QSlotObjectBase(&impl), function(f) {} }; - // typedefs for readability for when there are no parameters - template <typename Func> - using QSlotObjectWithNoArgs = QFunctorSlotObject<Func, - QtPrivate::List<>, - typename QtPrivate::FunctionPointer<Func>::ReturnType>; - - template <typename Func, typename R> - using QFunctorSlotObjectWithNoArgs = QFunctorSlotObject<Func, QtPrivate::List<>, R>; - - template <typename Func> - using QFunctorSlotObjectWithNoArgsImplicitReturn = QFunctorSlotObjectWithNoArgs<Func, typename QtPrivate::FunctionPointer<Func>::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, diff --git a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp index b8f808ddfe..e721187b1e 100644 --- a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp +++ b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp @@ -8561,6 +8561,30 @@ void tst_QObject::asyncCallbackHelper() QVERIFY(caller.callMe0(mutableLambda2)); // this copies the lambda caller.slotObject->call(nullptr, argv); // this call doesn't change mutableLambda2 QCOMPARE(mutableLambda2(), 2); // so we are still at 2 + + { + int called = -1; + struct MutableFunctor { + void operator()() { called = 0; } + int &called; + }; + struct ConstFunctor + { + void operator()() const { called = 1; } + int &called; + }; + + MutableFunctor mf{called}; + QMetaObject::invokeMethod(this, mf); + QCOMPARE(called, 0); + ConstFunctor cf{called}; + QMetaObject::invokeMethod(this, cf); + QCOMPARE(called, 1); + QMetaObject::invokeMethod(this, [&called, u = std::unique_ptr<int>()]{ called = 2; }); + QCOMPARE(called, 2); + QMetaObject::invokeMethod(this, [&called, count = 0]() mutable { called = 3; }); + QCOMPARE(called, 3); + } } QTEST_MAIN(tst_QObject) |