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:
Volker Hilsheimer 2023-05-02 17:39:34 +02:00
parent 2162e0dfc4
commit 3bf5b5f894
3 changed files with 71 additions and 54 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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)