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>
This commit is contained in:
parent
2162e0dfc4
commit
3bf5b5f894
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user