summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVolker Hilsheimer <volker.hilsheimer@qt.io>2023-05-02 17:39:34 +0200
committerVolker Hilsheimer <volker.hilsheimer@qt.io>2023-05-05 21:45:56 +0200
commit3bf5b5f8944dd417530b09dd6f1cd568717c8cc1 (patch)
treee6193d7444330b00bacb60c4d3bfafb6b9f2e96b
parent2162e0dfc4b714d24c5610cab1e37e0e0cf4c74e (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.h43
-rw-r--r--src/corelib/kernel/qobjectdefs_impl.h58
-rw-r--r--tests/auto/corelib/kernel/qobject/tst_qobject.cpp24
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)