Support free functions and const functors as callbacks
Amend 207aae5560
, as code checker
complained that we std::move'd a potential lvalue. This warning was
valid if the public API did not accept the functor parameter by value.
Fix this by consistently std::forward'ing the parameters through the
call stack, and add a compile-time test. Writing that test revealed that
the helper API didn't work with free functions, so fix that as well. It
also revealed that QFunctorSlotObject couldn't work with a const
functor, which is also fixed by this change.
We cannot support move-only functors with that change, as it requires
a change to QFunctorSlotObject that breaks the QMetaObject test.
Change-Id: Iafd747baf4cb0213ecedb391ed46b4595388182b
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
10a850f584
commit
bd69821074
@ -123,20 +123,20 @@ public:
|
||||
template <typename Functor>
|
||||
void requestPermission(const QPermission &permission,
|
||||
const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *receiver,
|
||||
Functor func)
|
||||
Functor &&func)
|
||||
{
|
||||
using Prototype = void(*)(QPermission);
|
||||
requestPermission(permission,
|
||||
QtPrivate::makeSlotObject<Prototype>(std::move(func)),
|
||||
QtPrivate::makeSlotObject<Prototype>(std::forward<Functor>(func)),
|
||||
receiver);
|
||||
}
|
||||
# endif // Q_QDOC
|
||||
|
||||
// requestPermission to a functor or function pointer (without context)
|
||||
template <typename Functor>
|
||||
void requestPermission(const QPermission &permission, Functor func)
|
||||
void requestPermission(const QPermission &permission, Functor &&func)
|
||||
{
|
||||
requestPermission(permission, nullptr, std::move(func));
|
||||
requestPermission(permission, nullptr, std::forward<Functor>(func));
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -342,15 +342,19 @@ namespace QtPrivate {
|
||||
constexpr int inline countMatchingArguments()
|
||||
{
|
||||
using ExpectedArguments = typename QtPrivate::FunctionPointer<Prototype>::Arguments;
|
||||
using Actual = std::decay_t<Functor>;
|
||||
|
||||
if constexpr (QtPrivate::FunctionPointer<Functor>::IsPointerToMemberFunction) {
|
||||
using ActualArguments = typename QtPrivate::FunctionPointer<Functor>::Arguments;
|
||||
if constexpr (QtPrivate::FunctionPointer<Actual>::IsPointerToMemberFunction
|
||||
|| QtPrivate::FunctionPointer<Actual>::ArgumentCount >= 0) {
|
||||
// PMF or free function
|
||||
using ActualArguments = typename QtPrivate::FunctionPointer<Actual>::Arguments;
|
||||
if constexpr (QtPrivate::CheckCompatibleArguments<ExpectedArguments, ActualArguments>::value)
|
||||
return QtPrivate::FunctionPointer<Functor>::ArgumentCount;
|
||||
return QtPrivate::FunctionPointer<Actual>::ArgumentCount;
|
||||
else
|
||||
return -1;
|
||||
} else {
|
||||
return QtPrivate::ComputeFunctorArgumentCount<Functor, ExpectedArguments>::Value;
|
||||
// lambda or functor
|
||||
return QtPrivate::ComputeFunctorArgumentCount<Actual, ExpectedArguments>::Value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -488,7 +492,7 @@ namespace QtPrivate {
|
||||
template <typename Prototype, typename Functor>
|
||||
static constexpr std::enable_if_t<QtPrivate::countMatchingArguments<Prototype, Functor>() >= 0,
|
||||
QtPrivate::QSlotObjectBase *>
|
||||
makeSlotObject(Functor &&func)
|
||||
makeSlotObject(Functor func)
|
||||
{
|
||||
using ExpectedSignature = QtPrivate::FunctionPointer<Prototype>;
|
||||
using ExpectedArguments = typename ExpectedSignature::Arguments;
|
||||
@ -496,15 +500,16 @@ namespace QtPrivate {
|
||||
|
||||
static_assert(int(ActualSignature::ArgumentCount) <= int(ExpectedSignature::ArgumentCount),
|
||||
"Functor requires more arguments than what can be provided.");
|
||||
constexpr int MatchingArgumentCount = QtPrivate::countMatchingArguments<Prototype, Functor>();
|
||||
|
||||
if constexpr (QtPrivate::FunctionPointer<Functor>::IsPointerToMemberFunction) {
|
||||
using ActualArguments = typename ActualSignature::Arguments;
|
||||
return new QtPrivate::QSlotObject<Functor, ActualArguments, void>(func);
|
||||
return new QtPrivate::QSlotObject<Functor, ActualArguments, void>(func);
|
||||
} else {
|
||||
constexpr int MatchingArgumentCount = QtPrivate::countMatchingArguments<Prototype, Functor>();
|
||||
using ActualArguments = typename QtPrivate::List_Left<ExpectedArguments, MatchingArgumentCount>::Value;
|
||||
|
||||
return new QtPrivate::QFunctorSlotObject<Functor, MatchingArgumentCount, ActualArguments, void>(std::move(func));
|
||||
return new QtPrivate::QFunctorSlotObject<Functor, MatchingArgumentCount,
|
||||
ActualArguments, void>(std::move(func));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,20 +63,20 @@ public:
|
||||
template <typename Functor>
|
||||
static inline int lookupHost(const QString &name,
|
||||
const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *receiver,
|
||||
Functor func)
|
||||
Functor &&func)
|
||||
{
|
||||
using Prototype = void(*)(QHostInfo);
|
||||
return lookupHostImpl(name, receiver,
|
||||
QtPrivate::makeSlotObject<Prototype>(std::move(func)),
|
||||
QtPrivate::makeSlotObject<Prototype>(std::forward<Functor>(func)),
|
||||
nullptr);
|
||||
}
|
||||
#endif // Q_QDOC
|
||||
|
||||
// lookupHost to a callable (without context)
|
||||
template <typename Functor>
|
||||
static inline int lookupHost(const QString &name, Functor slot)
|
||||
static inline int lookupHost(const QString &name, Functor &&slot)
|
||||
{
|
||||
return lookupHost(name, nullptr, std::move(slot));
|
||||
return lookupHost(name, nullptr, std::forward<Functor>(slot));
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -149,6 +149,7 @@ private slots:
|
||||
void objectNameBinding();
|
||||
void emitToDestroyedClass();
|
||||
void declarativeData();
|
||||
void asyncCallbackHelper();
|
||||
};
|
||||
|
||||
struct QObjectCreatedOnShutdown
|
||||
@ -8355,5 +8356,152 @@ void tst_QObject::declarativeData()
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
Compile-time test for the helpers in qobjectdefs_impl.h.
|
||||
*/
|
||||
class AsyncCaller : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
void callback0() {}
|
||||
void callback1(const QString &) {}
|
||||
void callbackInt(int) {}
|
||||
int returnInt() const { return 0; }
|
||||
|
||||
static void staticCallback0() {}
|
||||
static void staticCallback1(const QString &) {}
|
||||
|
||||
using Prototype0 = void(*)();
|
||||
using Prototype1 = void(*)(QString);
|
||||
|
||||
template<typename Functor>
|
||||
bool callMe0(const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *, Functor &&func)
|
||||
{
|
||||
auto *slotObject = QtPrivate::makeSlotObject<Prototype0>(std::forward<Functor>(func));
|
||||
slotObject->destroyIfLastRef();
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Functor>
|
||||
bool callMe0(Functor &&func)
|
||||
{
|
||||
return callMe0(nullptr, std::forward<Functor>(func));
|
||||
}
|
||||
|
||||
template<typename Functor>
|
||||
bool callMe1(const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *, Functor &&func)
|
||||
{
|
||||
auto *slotObject = QtPrivate::makeSlotObject<Prototype1>(std::forward<Functor>(func));
|
||||
slotObject->destroyIfLastRef();
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Functor>
|
||||
bool callMe1(Functor &&func)
|
||||
{
|
||||
return callMe1(nullptr, std::forward<Functor>(func));
|
||||
}
|
||||
};
|
||||
|
||||
static void freeFunction0() {}
|
||||
static void freeFunction1(QString) {}
|
||||
|
||||
template<typename Prototype, typename Functor, typename = void>
|
||||
struct AreFunctionsCompatible : std::false_type {};
|
||||
template<typename Prototype, typename Functor>
|
||||
struct AreFunctionsCompatible<Prototype, Functor, std::enable_if_t<
|
||||
std::is_same_v<decltype(QtPrivate::makeSlotObject<Prototype>(std::forward<Functor>(std::declval<Functor>()))),
|
||||
QtPrivate::QSlotObjectBase *>>
|
||||
> : std::true_type {};
|
||||
|
||||
template<typename Prototype, typename Functor>
|
||||
inline constexpr bool compiles(Functor &&) {
|
||||
return QtPrivate::AreFunctionsCompatible<Prototype, Functor>::value;
|
||||
}
|
||||
|
||||
void tst_QObject::asyncCallbackHelper()
|
||||
{
|
||||
auto lambda0 = []{};
|
||||
auto lambda1 = [](const QString &) {};
|
||||
auto lambda2 = [](const QString &, int) {};
|
||||
const auto constLambda = [](const QString &) {};
|
||||
auto moveOnlyLambda = [u = std::unique_ptr<int>()]{};
|
||||
|
||||
SlotFunctor functor0;
|
||||
SlotFunctorString functor1;
|
||||
|
||||
// no parameters provided or needed
|
||||
static_assert(compiles<AsyncCaller::Prototype0>(&AsyncCaller::callback0));
|
||||
static_assert(compiles<AsyncCaller::Prototype0>(&AsyncCaller::staticCallback0));
|
||||
static_assert(compiles<AsyncCaller::Prototype0>(lambda0));
|
||||
static_assert(compiles<AsyncCaller::Prototype0>(freeFunction0));
|
||||
static_assert(compiles<AsyncCaller::Prototype0>(functor0));
|
||||
|
||||
// more parameters than needed
|
||||
static_assert(compiles<AsyncCaller::Prototype1>(&AsyncCaller::callback0));
|
||||
static_assert(compiles<AsyncCaller::Prototype1>(&AsyncCaller::staticCallback0));
|
||||
static_assert(compiles<AsyncCaller::Prototype1>(lambda0));
|
||||
static_assert(compiles<AsyncCaller::Prototype1>(freeFunction0));
|
||||
static_assert(compiles<AsyncCaller::Prototype1>(functor0));
|
||||
|
||||
// matching parameter
|
||||
static_assert(compiles<AsyncCaller::Prototype1>(&AsyncCaller::callback1));
|
||||
static_assert(compiles<AsyncCaller::Prototype1>(&AsyncCaller::staticCallback1));
|
||||
static_assert(compiles<AsyncCaller::Prototype1>(lambda1));
|
||||
static_assert(compiles<AsyncCaller::Prototype1>(constLambda));
|
||||
static_assert(compiles<AsyncCaller::Prototype1>(freeFunction1));
|
||||
static_assert(compiles<AsyncCaller::Prototype1>(functor1));
|
||||
|
||||
// not enough parameters
|
||||
static_assert(!compiles<AsyncCaller::Prototype0>(&AsyncCaller::callback1));
|
||||
static_assert(!compiles<AsyncCaller::Prototype0>(&AsyncCaller::staticCallback1));
|
||||
static_assert(!compiles<AsyncCaller::Prototype0>(lambda1));
|
||||
static_assert(!compiles<AsyncCaller::Prototype0>(constLambda));
|
||||
static_assert(!compiles<AsyncCaller::Prototype0>(lambda2));
|
||||
static_assert(!compiles<AsyncCaller::Prototype0>(freeFunction1));
|
||||
static_assert(!compiles<AsyncCaller::Prototype0>(functor1));
|
||||
|
||||
// move-only functor - should work, but doesn't because QFunctorSlotObject requires
|
||||
// the functor to be of a copyable type!
|
||||
static_assert(!compiles<AsyncCaller::Prototype0>(moveOnlyLambda));
|
||||
static_assert(!compiles<AsyncCaller::Prototype1>(moveOnlyLambda));
|
||||
|
||||
// wrong parameter type
|
||||
static_assert(!compiles<AsyncCaller::Prototype1>(&AsyncCaller::callbackInt));
|
||||
|
||||
// old-style slot name
|
||||
static_assert(!compiles<AsyncCaller::Prototype0>("callback1"));
|
||||
|
||||
// slot with return value is ok, we just don't pass
|
||||
// the return value through to anything.
|
||||
static_assert(compiles<AsyncCaller::Prototype0>(&AsyncCaller::returnInt));
|
||||
|
||||
AsyncCaller caller;
|
||||
// with context
|
||||
QVERIFY(caller.callMe0(&caller, &AsyncCaller::callback0));
|
||||
QVERIFY(caller.callMe0(&caller, &AsyncCaller::returnInt));
|
||||
QVERIFY(caller.callMe0(&caller, &AsyncCaller::staticCallback0));
|
||||
QVERIFY(caller.callMe0(&caller, lambda0));
|
||||
QVERIFY(caller.callMe0(&caller, freeFunction0));
|
||||
// QVERIFY(caller.callMe0(&caller, moveOnlyLambda));
|
||||
|
||||
QVERIFY(caller.callMe1(&caller, &AsyncCaller::callback1));
|
||||
QVERIFY(caller.callMe1(&caller, &AsyncCaller::staticCallback1));
|
||||
QVERIFY(caller.callMe1(&caller, lambda1));
|
||||
QVERIFY(caller.callMe1(&caller, freeFunction1));
|
||||
QVERIFY(caller.callMe1(&caller, constLambda));
|
||||
|
||||
// without context
|
||||
QVERIFY(caller.callMe0(&AsyncCaller::staticCallback0));
|
||||
QVERIFY(caller.callMe0(lambda0));
|
||||
QVERIFY(caller.callMe0(freeFunction0));
|
||||
// QVERIFY(caller.callMe0(moveOnlyLambda));
|
||||
|
||||
QVERIFY(caller.callMe1(&AsyncCaller::staticCallback1));
|
||||
QVERIFY(caller.callMe1(lambda1));
|
||||
QVERIFY(caller.callMe1(constLambda));
|
||||
QVERIFY(caller.callMe1(freeFunction1));
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QObject)
|
||||
#include "tst_qobject.moc"
|
||||
|
Loading…
Reference in New Issue
Block a user